基本编程流程概述
要实现客户端和服务器端的通信,则需要客户端和服务器端共同完成。其中,TCP服务器和客户端的编程流程如图所示:
首先由服务器端创建socket,然后bind绑定通信端口,创建listen监听队列。之后便开始了通讯的过程。此时服务器阻塞在accept这里,直到tcp客户端主动进行connect进行三次握手建立连接。此时客户端的write函数向服务器发送请求,服务器端read来读取请求,并且进行处理。处理完成之后调用服务器端的write函数给客户端发送应答报文,客户端则通过read来接受回应信息。至此整个数据交换结束,客户端可以主动关闭。
客户端和服务器端的socket编程
其中family通常在IPV4协议下下是AF_INET,IPv6协议下是AF_INET6。type在TCP传输协议下是SOCK_STREAM,UDP协议下是SOCK_DGRAM.一般来说,函数socket的参数protocol设置为0,除非用在原始套接口上
客户端connect函数和服务器建立链接
TCP客户端用connect函数来和TCP服务器建立连接。
sockfd是socket函数的返回值。第二个参数是指向套接口地址结构体的指针,第三个参数是结构体的大小。
sin_port是端口号,S_addr是服务器网络ip地址。通过这段程序,客户端即可连接到客户端的端口和ip上,也就是建立链接。
bind绑定套接字和ip端口
bind给套接口分配一个本地协议地址,对于网络协议,协议地址是32位Ipv4地址或者128位Ipv6地址。
第一个参数是socket函数的返回值,第二个和第三个同connect函数中所描述的。
建立两个套接口对象,一个是面向服务器,一个面向客户端。
服务器listen监听队列
listen仅仅被TCP服务器调用,主要完成两个功能:
backlog指的是最大链接个数。对于一个给定的监听套接口,内核要维护两个队列:
1.以完成链接队列
2.未完成连接队列
backlog之前一直被定义为两个队列总和的最大值。通常试验环境下,将backlog设置成5。但是对于现代服务器来说是远远不够的。记住backlog不能设置成0,这会导致程序发生缺陷。如果不想要其他人来链接,要关闭此端口。
服务器端的accept
accept由服务器调用,用来链接客户端发送的connect请求,由此建立通过三次挥手建立连接。
请注意,如果accept执行成功,返回值是一个由内核自动生成的全新描述符,代表和客户端的TCP链接。 这个描述符称之为已连接套接字,而把socket函数的返回值称之为监听套接字。一个监听套接字sockfd往往在服务器程序中只生成一个,并且一直存在。而已连接套接字C代表着客户端和服务器完成了三次握手,当客户端断开时,则关闭C套接字。
当然,如果C<0,则代表已经建立链接失败,则会阻塞一直等待客户端去链接。
write和read
write和read是两种状态,分别代表处理数据和接收数据。举个例子,在程序进行通信的时候,通常write由send系统调用来表示,而read通常通过recv系统调用来表示。
客户端的send和recv:
服务器端的send和recv:
关闭套接字close
至此,TCP套接口的编程流程就基本结束,只需要关闭掉套接字描述符即可。
实验结果
三次握手建立链接:
持续读取数据和客户端发送数据:
服务器源代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=sockfd);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr)); //注意memset是取地址
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);//主机转网络字节,网络是大段;
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); //以完成3次握手队列长度是5
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
//这里是c也是套接字,类似与上面代码的sockfd;
if(c<0)
{
continue;
}
printf("accept c=%d\n",c); //链接套接子
while(1)
{
char buff[128]={
0};
int n=recv(c,buff,127,0);
//如果对方关闭了发送,recv返回0;-1是失败
if(n<=0)
{
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
}
客户端源代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main()
{
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);
while(1)
{
char buff[128]={
0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,128,0);
printf("buff=%s\n",buff);
}
exit(0);
}