TCP服务端和客户端的编程流程是网络编程的重点。以下内容包括编程步骤、代码实现、以及代码解析四个方面。
目录
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
",c,inet_ntoa(caddr.sin_addr));
char buf[128]={0};
int n = recv(c,buf,127,0);
printf("buf(%d)=%s
",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:
");
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
",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这两个结构体保持大小相同而预留的空字节。