这次是一个服务器与一个客户端可以多次连接,与上次有所不同的是让客户端可以持续发送数据与服务器端连接,不仅仅是之连接一次,下面我们直接给出代码,然后分析结果
//服务器端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
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 root
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//转换成无符号的整形
//bind()命名套接字的第二个参数那里是进行了强转,因为一般的用的都是通用地址结构,但我们现在用的是IPV4协议,所以要强转成IPV4专用的地址结构
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5); //创建监听队列,然后客户端connect通过三次握手建立连接然后服务器端accept接受连接
//当打开两个窗口的时候,你会发现在该程序中第一个客户端窗口什么都不输入并且不关闭它的时候第二个窗口即使输入内容客户端也不会收到服务器传来的ok,
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
printf("accept c = %d\n",c);
while(1) //在此处加一个循环就可以 实现一个客户端可以多次连接服务器端
{
char buff[128] = {0};
int n = recv(c,buff,127,0);
if(n <= 0) //如果客户端输入数据并且被接收了那n必然是>0的,如果客户端不输入数据那就会阻塞,n小于0是因为对方关闭了连接,所以我们用四次挥手借助返回值n = 0,来表示对方已经close
{
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<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.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");//这里写服务器端的ip,就例如打电话你肯定拨打的是对方的手机号
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) //以end结束
{
break;
}
//将输入的数据发送给服务器端然后清空buff,以便存放服务器端发送来的确认信息ok
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff = %s\n",buff);
}
close(sockfd);
exit(0);
}
我们同时打开两个客户端端口然后看运行结果并分析:
第一个窗口不发送消息也不关闭他而是一直占用它的时候,如果第二个窗口发送消息是,它是不会收到服务器端的确认回复消息ok的,但是如果第一个客户端输入end,结束并且关闭第一个客户端的时候,第二个客户端你什么都不用输入,服务器就会将之前第二个客户端输入的内容依次打印出来,第二个客户端就会依次收到服务器端发来的确认信息ok。
分析:第一个客户端窗口不输入数据执行的时候程序是阻塞在了recv接收处,因为客户端没有发送数据所以服务器端接收不上数据,第二个客户端窗口开始发送数据但是服务器端没有回复确认信息,是因为第二个窗口是阻塞在了accept处,从代码角度讲,recv没有结束的时候该处的小循环就不能退出,上面的大循环执行不了那么c = accept就不能执行,理论上讲就是说现在的第二个客户端窗口是已经完成三次握手的,在已完成三次握手的队列中等待accept去处理它的,但是由于前面的阻塞,accept前一个还没有处理完它现在顾不上第二个客户端的处理,所以在accept处阻塞,在Listen(sockfd,5)这个数可以是5到128之间的任何数,该处是不会产生阻塞的,如果完成三次握手的队列超过这个长度,则服务器端请求连接失败
第二个客户端为什么会依次将输入的信息全部打印?是因为之前第二个客户端输入的信息都是存在已完成第三次握手的队列中,等待accept去处理的,现在accept有空的时候会一一处理,accept处理一个recv就接收一个然后打印出来,然后accept再接受处理,这里要和发送/接收缓冲区、流式服务区分开。
为什么端口号是6000?答:
端口值:1024以内叫知名端口,只有root管理员可以使用, 例如:http使用80
1024——4096 叫保留端口
>4096 叫临时端口 应用程序使用的(我们自己写程序)
为什么第二个客户端的ok也会依次全部打印,input直接跳过呢?没来的及让客户端输入。
返回的文件描述符 c = 4,是因为前面的都被占用,0标准输入、1标准输出、2标准错误、3是前面的套接字sockfd占用,所以c是4,(文件描述符总是使用的是最小未被使用的)。