1. TCP的连接与断开
1.1 创建连接过程(三次握手)
 * 客户端向服务器发送连接请求SYN
 * 服务器接收到连接请求SYN后, 向客户端发送收到指令ACK和连接请求SYN
 * 客户端收到服务器发送的ACK和SYN后向服务器发送收到指令ACK 
1.2 断开连接过程(四次挥手)
 * 客户端向服务器发送断开请求FIN
 * 服务器接收到客户端发送的断开请求FIN后向客户端发送收到指令ACK
 * 服务器检查是否还有没有收发完的数据, 如果数据已经收发完毕, 服务器向客户端发送断开请求FIN
 * 客户端接收到服务器发来的断开请求后, 检查是否还有没有接收完的数据,如果没有就向服务器发送收到指令ACK 
 2. TCP与UDP的区别
 * TCP有连接, UDP没有连接
 * TCP是数据流, UDP是数据报文
 * TCP收发数据相对慢, UDP收发数据相对快(局域网内传输数据用UDP相对较好,它可以极大限度地利用带宽)
 * TCP安全,稳定,可靠;UDP不安全,不稳定,不可靠(安全: 数据相对不容易被窃取    稳定: 几乎没有传输速率的变化   可靠: 一定能收到数据)
 * TCP有序(先发送的数据先到, 后发送的数据后到), 数据有边界;UDP无序(可能后发送的数据会先到),数据无边界 
3. IO多路复用之select
3.1 select函数
//select函数原型 //监视放在里面的描述符号,有反应返回1, 没有反应返回-1 int select(int nfds, 
//描述符号数量,最大描述符号数加一 fd_set *readfds, //描述符号集合(读取) fd_set *writefds, //描述符号集合(写入) 
fd_set *exceptfds, //描述符号集合(异常) struct timeval *timeout); //延时 void FD_CLR(int 
fd,fd_set *set); //将fd从set中删除 int FD_ISSET(int fd,fd_set *set); 
//判断fd是否在set中(是返回非0,否返回0) void FD_SET(int fd,fd_set *set); //将fd添加到set中 void 
FD_ZERO(fd_set *set); //将set置为0(清空) 
3.2 select函数实现监视标准输入 0  
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include 
<sys/select.h> #include <fcntl.h> int main(){ fd_set fds; //描述符号集合 
FD_ZERO(&fds); //置零 FD_SET(0,&fds); //将标准输入设备 0 添加到描述符号集合 int r; char 
buff[1024] = {0}; while(1){ //使用一次阻塞替代多次阻塞 r = select(1,&fds,NULL,NULL,NULL); 
if(r > 0){ printf("%d有动静!\n",r); scanf("%s",buff); printf("接收到了:%s\n",buff); } 
} return 0; } 
 
3.3 select函数实现服务器连接多个客户端
 服务器(server)端
//服务器端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include 
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include 
<string.h> #include <signal.h> #include <sys/select.h> #include <fcntl.h> 
//最多允许的客户端数量 #define NUM 100 int serverSocket,clientSocket[NUM]; int currentNum 
= 0; //当前客户端数量 void hand(int val){ //7. 关闭连接 for(int i = 0;i < NUM; i++){ if(-1 
!= clientSocket[i]) close(clientSocket[i]); } close(serverSocket); printf("bye 
bye!\n"); exit(0); } int main(int argc,char* argv[]){ if(argc != 3) 
printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s 
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一: 
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 serverSocket = socket(AF_INET,SOCK_STREAM,0); 
if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1); 
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in sAddr = { 0 }; 
sAddr.sin_family = AF_INET; //协议类型 和socket函数第一个参数一致 sAddr.sin_addr.s_addr = 
inet_addr(argv[1]); //将字符串转整数 sAddr.sin_port = htons(atoi(argv[2])); 
//将字符串转整数,再将小端转换成大端 //3. 绑定服务器协议地址簇 int r = bind(serverSocket,(struct 
sockaddr*)&sAddr,sizeof sAddr); if(-1 == r) 
printf("绑定失败:%m\n"),close(serverSocket),exit(-2); printf("绑定成功!\n"); //4. 监听 r 
= listen(serverSocket,10); //数量 if(-1 == r) 
printf("监听失败:%m\n"),close(serverSocket),exit(-3); printf("监听成功!\n"); 
//初始化客户端描述符号数组 for (int i = 0; i < NUM; ++i){ clientSocket[i] = -1; } //开始监视 
//不仅需要监视serverSocket还要监视每一个返回回来的clientSocket fd_set fds; int maxFd; //最大描述符号 
struct sockaddr_in cAddr = {0}; int len = sizeof(cAddr); int cfd; char 
buff[1024] = {0}; maxFd = 0; maxFd = ((maxFd > serverSocket) ? maxFd : 
serverSocket); while(1){ FD_ZERO(&fds); //清空 FD_SET(serverSocket,&fds); 
//将服务器socketFd放到监视集合之中 //将客户端socketFd放到监视集合之中 for (int i = 0; i < NUM; ++i){ 
if(-1 != clientSocket[i]){ FD_SET(clientSocket[i],&fds); } } //开始监视 r = 
select(maxFd+1,&fds,NULL,NULL,NULL); if(-1 == r) 
printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1); else if(0 == r){ 
printf("服务器处于等待状态!\n"); continue; }else{ //检查是不是serverSocket的动静 
if(FD_ISSET(serverSocket,&fds)){ cfd = accept(serverSocket,NULL,NULL); if(-1 == 
cfd){ printf("客户端连接失败!\n"); }else{ printf("有客户端连接上服务器了:%d\n",cfd); //保存客户端描述符号 
for (int i = 0; i < NUM; ++i){ if(-1 == clientSocket[i]){ clientSocket[i] = 
cfd; maxFd = ((maxFd > cfd) ? maxFd : cfd); break; } } } } } //检查客户端是否有动静 for 
(int i = 0; i < NUM; ++i){ if(-1 != clientSocket[i] && 
FD_ISSET(clientSocket[i],&fds)){ r = recv(clientSocket[i],buff,1023,0); if(r > 
0){ buff[r] = 0; printf("%d >> %s\n",clientSocket[i], buff); }else{ 
printf("客户端: %d 已经断开连接了\n",clientSocket[i]); clientSocket[i] = -1; } } } } 
return 0; } 
 客户(Client)端
//客户端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include 
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include 
<string.h> #include <signal.h> int clientSocket; void hand(int val){ //5. 关闭连接 
close(clientSocket); printf("bye bye!\n"); exit(0); } int main(int argc,char* 
argv[]){ if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s 
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一: 
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 clientSocket = socket(AF_INET,SOCK_STREAM,0); 
if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1); 
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in cAddr = { 0 }; 
cAddr.sin_family = AF_INET; cAddr.sin_addr.s_addr = inet_addr(argv[1]); 
//将字符串转整数 cAddr.sin_port = htons(atoi(argv[2])); //将字符串转整数,再将小端转换成大端 //3.连接服务器 
int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr); if(-1 == 
r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2); printf("连接服务器成功!\n"); 
//4. 通信 char buff[256] = {0}; while(1){ printf("你想要发送:"); scanf("%s",buff); 
send(clientSocket,buff,strlen(buff),0); } return 0; } 
 
3.4 select函数实现简单聊天室 
 服务器(Server)端
//服务器端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include 
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include 
<string.h> #include <signal.h> #include <sys/select.h> #include <fcntl.h> 
//最多允许的客户端数量 #define NUM 100 int serverSocket,clientSocket[NUM]; int currentNum 
= 0; //当前客户端数量 void hand(int val){ //7. 关闭连接 for(int i = 0;i < NUM; i++){ if(-1 
!= clientSocket[i]) close(clientSocket[i]); } close(serverSocket); printf("bye 
bye!\n"); exit(0); } int main(int argc,char* argv[]){ if(argc != 3) 
printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s 
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一: 
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 serverSocket = socket(AF_INET,SOCK_STREAM,0); 
if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1); 
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in sAddr = { 0 }; 
sAddr.sin_family = AF_INET; //协议类型 和socket函数第一个参数一致 sAddr.sin_addr.s_addr = 
inet_addr(argv[1]); //将字符串转整数 sAddr.sin_port = htons(atoi(argv[2])); 
//将字符串转整数,再将小端转换成大端 //3. 绑定服务器协议地址簇 int r = bind(serverSocket,(struct 
sockaddr*)&sAddr,sizeof sAddr); if(-1 == r) 
printf("绑定失败:%m\n"),close(serverSocket),exit(-2); printf("绑定成功!\n"); //4. 监听 r 
= listen(serverSocket,10); //数量 if(-1 == r) 
printf("监听失败:%m\n"),close(serverSocket),exit(-3); printf("监听成功!\n"); 
//初始化客户端描述符号数组 for (int i = 0; i < NUM; ++i){ clientSocket[i] = -1; } //开始监视 
//不仅需要监视serverSocket还要监视每一个返回回来的clientSocket fd_set fds; int maxFd; //最大描述符号 
struct sockaddr_in cAddr = {0}; int len = sizeof(cAddr); int cfd; char 
buff[1024] = {0}; maxFd = 0; maxFd = ((maxFd > serverSocket) ? maxFd : 
serverSocket); while(1){ FD_ZERO(&fds); //清空监视集合 FD_SET(serverSocket,&fds); 
//将服务器socketFd放到监视集合之中 //将客户端socketFd放到监视集合之中 for (int i = 0; i < NUM; ++i){ 
if(-1 != clientSocket[i]){ FD_SET(clientSocket[i],&fds); } } //开始监视 r = 
select(maxFd+1,&fds,NULL,NULL,NULL); if(-1 == r) 
printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1); else if(0 == r){ 
printf("服务器处于等待状态!\n"); continue; }else{ //检查是不是serverSocket的动静 
if(FD_ISSET(serverSocket,&fds)){ cfd = accept(serverSocket,NULL,NULL); if(-1 == 
cfd){ printf("客户端连接失败!\n"); }else{ printf("有客户端连接上服务器了:%d\n",cfd); //保存客户端描述符号 
for (int i = 0; i < NUM; ++i){ if(-1 == clientSocket[i]){ clientSocket[i] = 
cfd; maxFd = ((maxFd > cfd) ? maxFd : cfd); break; } } } } } //检查客户端是否有动静 for 
(int i = 0; i < NUM; ++i){ if(-1 != clientSocket[i] && 
FD_ISSET(clientSocket[i],&fds)){ r = recv(clientSocket[i],buff,1023,0); if(r > 
0){ buff[r] = 0; printf("%d >> %s\n",clientSocket[i], buff); 
//服务器将数据转发给每一个在线的客户端(除了发消息给服务器的客户端) char tBuff[2048]; 
sprintf(tBuff,"来自%d客户端发给服务器的消息:%s",clientSocket[i],buff); for(int j = 0; j < 
NUM; j++){ if(-1 != clientSocket[j] && clientSocket[i] != clientSocket[j]){ 
send(clientSocket[j],tBuff,strlen(tBuff),0); } } }else{ printf("客户端: %d 
已经断开连接了\n",clientSocket[i]); clientSocket[i] = -1; } } } } return 0; } 
 客户(Client)端
 
//客户端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include 
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include 
<string.h> #include <signal.h> int clientSocket; void hand(int val){ //5. 关闭连接 
close(clientSocket); printf("bye bye!\n"); exit(0); } int main(int argc,char* 
argv[]){ if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s 
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一: 
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 clientSocket = socket(AF_INET,SOCK_STREAM,0); 
if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1); 
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in cAddr = { 0 }; 
cAddr.sin_family = AF_INET; cAddr.sin_addr.s_addr = inet_addr(argv[1]); 
//将字符串转整数 cAddr.sin_port = htons(atoi(argv[2])); //将字符串转整数,再将小端转换成大端 //3.连接服务器 
int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr); if(-1 == 
r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2); printf("连接服务器成功!\n"); 
//开始监视 //不仅要监视标准输入设备, 还要监视clientSocket服务器是否发送数据 fd_set fds; int maxFd = 
clientSocket > 0 ? clientSocket : 0; char buff[2048] = {0}; while(1){ //清空集合 
FD_ZERO(&fds); //将标准输入输出放入到集合中 FD_SET(0,&fds); //将clientSocket放入到监视集合中 
FD_SET(clientSocket,&fds); //开始监视 r = select(maxFd + 1, &fds, NULL,NULL,NULL); 
if(-1 == r) printf("客户端崩溃:%m\n"),close(clientSocket),exit(-1); else if(0 == r){ 
printf("客户端处于等待状态!\n"); continue; }else{ memset(buff,0,2048); //如果 0 
有动静就向服务器发消息 if(FD_ISSET(0,&fds)){ scanf("%s",buff); 
send(clientSocket,buff,strlen(buff),0); continue; } //如果 
clientSocket有动静就接收服务器发来的消息 if(FD_ISSET(clientSocket,&fds) && -1 != 
clientSocket){ memset(buff,0,2048); printf("服务器发来了客户端的消息!\n"); r = 
recv(clientSocket,buff,2047,0); if(r > 0){ buff[r] = 0; printf("服务器发来消息 >> 
%s\n",buff); } } } } return 0; }