TCP服务端和客户端的编程流程是网络编程的重点。以下内容包括编程步骤、代码实现、以及代码解析四个方面。

目录

1 编程步骤

2 代码实现

3 代码解析

 


1 编程步骤

tcp服务器与客户端的编程流程如下所示:

左边为服务端,右边为客户端。

我们常见的服务器客户端大多都是遵循这个步骤的。

2 代码实现

我们按照上述步骤来实现一个简易的服务端与客户端:

ser.c



# include<stdio.h>
# include<unistd.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
int main()
{
	//创建套接子
	//AF_INFT表示ipv4  SOCKET_STREAM表示传输层使用tcp协议 
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	//绑定
	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);//1024知名  4096保留 5000之上,临时端口
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	listen(sockfd,5);//开机

	while(1)
	{
		int len = sizeof(caddr);
		int c =accept(sockfd,(struct sockaddr*)&caddr,&len);
		if(c<0)
		{
			continue;
		}
		printf("c=%d,ip=%s\n",c,inet_ntoa(caddr.sin_addr));

		char buf[128]={0};
		int n = recv(c,buf,127,0);
		printf("buf(%d)=%s\n",n,buf);
		send(c,"ok",2,0);

		close(c);//待机
	}

	


	exit(0);

}

cli.c



# include<stdio.h>
# include<unistd.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
int main()
{
	//创建套接子
	//AF_INFT表示ipv4  SOCKET_STREAM表示传输层使用tcp协议 
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);
	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr =inet_addr("127.0.0.1");
	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);
	
	printf("input:\n");
	char buf[128]= {0};
	fgets(buf,128,stdin);
	send(sockfd,buf,strlen(buf),0);
	memset(buf,0,128);
	recv(sockfd,buf,127,0);
	printf("buf=%s\n",buf);
	close(sockfd);
	

	exit(0);

}	

程序运行结果如下图所示:

3 代码解析

从下面几个问题出发,对代码进行解析:

  • 服务端中listen(sockfd,5)函数的第二个参数5代表什么意思?

首先,listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态,那么第二个参数的含义为完成三次握手建立连接的队列的长度,5就代表在等待服务器的客户端

  • 三次握手和四次挥手分别发生在什么时候

当客户端执行connect函数的时候,开始进行三次握手。当某一端close的时候,开始四次挥手。当两端同时close的时候,四次挥手会演变为3次。

  • sockaddr_in与sockaddr结构体

sockaddr如下:

struct sockaddr {
unsigned short sa_family;     /* address family, AF_xxx */
char sa_data[14];                 /* 14 bytes of protocol address */
};

sa_family是地址家族,sa_data是一个14字节的协议地址。

sockaddr_in如下:

struct sockaddr_in {
short int sin_family;            /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;              /* Internet address */u
nsigned char sin_zero[8];         /* Same size as struct sockaddr */
};

在上述代码中我们可以发现,sockaddr和sockaddr_in可以相互转换,这是因为sockaddr是通用的套接字地址,而sockaddr_in是网络环境下的套接字地址,两个结构体的长度一样。sockaddr_in结构体中的元素sin_zero就是为了让sockaddr与sockaddr_in这两个结构体保持大小相同而预留的空字节。

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐