计算机网络基础及网络编程
依然数据,进程间通信的范畴。 网络通信是不同主机间的进程间通信。
1、OSI 模型 ===》开放系统互联模型 ==》分为7层: open system
interconnect
应用层:为网络用户提供各种服务,例如电子邮件、文件传输等。
表示层:为不同主机间的通信提供统一的数据表示形式。
会话层:负责信息传输的组织和协调,管理进程会话过程。
传输层:管理网络通信两端的数据传输,提供可靠或不可靠的传输服务。
网络层:负责数据传输的路由选择和网际互连。
数据链路层,负责物理相邻(通过网络介质相连)的主机间的数据传输,主要作用包括物理地址寻址、
数据帧封装、差错控制等。该层可分为逻辑链路控制子层(LLC)和介质访问控制子层(MAC)。
物理层,负责把主机中的数据转换成电信号,再通过网络介质(双绞线、光纤、无线信道等)来传输。
该层描述了信设备的机械、电气、功能等特性。
2.tcp/ip 模型
tcp/ip模型,是从osi 模型简化而来。
tcp/ip协议栈
应用层 ====》应用程序
传输层 ====》端口号tcp udp
网络层 ====》IP 地址
接口层 ====》网卡 驱动 1GB
3.常用网络协议
应用层: HTTP TFTP FTP SNMP DNS ...
传输层: TCP UDP tcp 传输控制协议 udp 用户数据报协议
网络层: IP ICMP(ping) RIP OSPF IGMP ...
接口层 : ARP RARP ... ip--->mac 地址解析
4.ip组成
IP地址 == 网络位 + 主机位
IP地址的分类: 点分十进制 ipv4 712934
A类: 超大规模性网络
8 8 8 8
1.0.0.0 - 126.255.255.255 126.1.1.1
126.1.1.2
255.0.0.0
私有:
10.0.0.0 - 10.255.255.255
127.0.0.1
B类: 大中规模型网络
128.0.0.0 - 191.255.255.255
128.2.1.2 128.2.7.2
255.255.0.0
私有:
172.16.0.0 - 172.31.255.255
5. 网络常用命令
C类: 中小规模型网络
192.0.0.0 - 223.255.255.255
255.255.255.0
私有:
192.168.0.0 - 192.168.255.255
静态路由
192.168.0.0
192.168.0.1 网关
192.168.0.255
D类: 组播和广播
224.0.0.0 - 239.255.255.255
192.168.0.255 == 255.255.255.255
235.1.2.3
192.168.1.0
192.168.0.1 网关
192.168.1.255 广播
E类: 实验
240.0.0.0 - 255.255.255.255
1.
ping 用于网络通断检测
ping www.baidu.com
2.
ifconfig
查看网络适配器的状态信息 查看自己的ip
3
netstat -anp
查看本机 所有的网络连接的状态
6. 名字解释
1、
socket 套接字 ,网络设备对应的文件描述符。
socket API application interface 用于网络通信的一组接口函数
2、ip+port
地址+端口===》地址用来识别主机
端口用来识别应用程序
port分为TCP port / UDP port 范围都是: 1-65535
3、
网络字节序 网络设备都是大端存储
主机字节序 都是小端存储
7.udp 用户数据报
1.特征
数据报
无连接 不可靠 网络延迟低 开销小
1.发送次数和接收次数要对应
2.发送和接收大小 ,保持一致。如果接收的大小小,剩下的数据就接收不到了(1包数据)。
3.每次发送数据,链路都是不同的。
4.对于udp而言,是有读阻塞。
5.没有写阻塞。发送方发送数据太快,接收发没有及时接收,就会出现丢包。
6.半双工

2.编程步骤

3.相关函数
int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符
参数:domain 地址族,PF_INET == AF_INET ==>互联网程序
PF_UNIX == AF_UNIX ==>单机程序
type 套接字类型:
SOCK_STREAM 流式套接字 ===》TCP
SOCK_DGRAM 用户数据报套接字===>UDP
SOCK_RAW 原始套接字 ===》IP
protocol 协议 ==》0 表示自动适应应用层协议。
返回值: 成功 返回申请的套接字id
失败 -1;
2、int bind(int sockfd, struct sockaddr *my_addr,socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将参数1相关
的文件描述符文件与参数2 指定的接口地址关联,
用于从该接口接受数据。
如果该函数在客户端调用,则表示要将数据从
参数1所在的描述符中取出并从参数2所在的接口
设备上发送出去。
注意:如果是客户端,则该函数可以省略,由默认
接口发送数据。
参数:sockfd 之前通过socket函数创建的文件描述符,套接字id
my_addr 是物理接口的结构体指针。表示该接口的信息。
struct sockaddr ////通用地址结构
{
u_short sa_family; ////地址族
char sa_data[14]; ////地址信息
};
转换成网络地址结构如下:
struct _sockaddr_in ///网络地址结构
{
u_short sin_family; ////地址族
u_short sin_port; ///地址端口
struct in_addr sin_addr; ///地址IP
char sin_zero[8]; ////占位
};
struct in_addr
{
in_addr_t s_addr;
}
socklen_t addrlen: 参数2 的长度。
返回值:成功 0
失败 -1;
发送接收函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:用于UDP协议中向对方发送数据。
参数:sockfd 本地的套接字id
buff 本地的数据存储,一般是要发送的数据。
len 要发送的数据长度
flags 要发送数据方式,0 表示阻塞发送。
dest_addr: 必选,表示要发送到的目标主机信息结构体。
addrlen :目标地址长度。
返回值:成功 发送的数据长度
失败 -1;
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:用于UDP协议中获取对方发送的数据。
参数:sockfd 本地的套接字id
buff 要存储数据的内存区,一般是数组或者动态内存。
len 要获取的数据长度,一般是buff的大小。
flags 获取方式,0 阻塞
src_addr 可选,表示对方的地址信息结构体,
下面是使用udp实现的服务端和客户端示例
server端:
// server 服务端
#include <bits/types/time_t.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr* (SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET,SOCK_DGRAM,0);
if (udpfd == -1)
{
perror("socket");
return 1;
}
// 2. 给套接字 绑定ip ,端口号
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser, cli; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("192.168.31.125");
//套接字绑定地址结构体
int ret = bind(udpfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
while (1)
{
char buf[1024] = {0};
//先从客户端收消息
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf,ctime(&tm));
//将信息写入,并发回客户端
sendto(udpfd, buf, sizeof(buf), 0, (SA)&cli, len);
}
return 0;
}
client端:
// client 客户端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("192.168.31.177");
while (1)
{
char buf[1024] = "hello,this is tcp test";
sendto(udpfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
bzero(buf, sizeof(buf));
//后两个参数为NULL是因为这是客户端,服务器对于客户端来说是唯一的
//所以不用指定从哪里接受信息
recvfrom(udpfd, buf, sizeof(buf), 0, NULL, NULL);
printf("from ser:%s\n", buf);
sleep(1);
}
close(udpfd);
return 0;
}
下面用udp实现文件的发送
server端发文件:c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
//通用地址结构体
typedef struct sockaddr* (SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser,cli; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址 //INADDR_ANY 表示本机地址 = 本机真是ip 一般写服务端
ser.sin_addr.s_addr = INADDR_ANY;
//套接字绑定地址结构体
int ret = bind(udpfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
char buf[1024] = {0};
//先从客户端收消息
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
printf("%s\n",buf);
int fd = open("1.jpeg", O_RDONLY);
if (fd == -1)
{
perror("open");
return 1;
}
char s[1024] = {0};
while (1)
{
bzero(s, sizeof(s));
ret = read(fd, s, sizeof(s));
if (ret == 0)
{
printf("文件发完了\n");
break;
}
sendto(udpfd, s, ret, 0, (SA)&cli, len);
bzero(s, sizeof(s));
//由于该程序是在本机间通信,读的速度大于写的速度,
//所以为了读写速度一致(读的次数等于写的次数),recvform能够在这阻塞,直到接收端发来信息再继续下一步的读文件
recvfrom(udpfd, s, ret, 0, (SA)&cli, &len);
//usleep(1000*10);
}
//由于接收段recvfrom在socket中没有内容时会阻塞,所以需要给接收段传信息,通知接收段结束
strcpy(s,"aaa");
sendto(udpfd, s, strlen(s), 0, (SA)&cli, len);
close(udpfd);
close(fd);
return 0;
}
client端收文件:
#include <fcntl.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <string.h>
typedef struct sockaddr* (SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("192.168.31.125");
char s[1024] = "请发文件";
sendto(udpfd, s, strlen(s), 0, (SA)&ser, sizeof(ser));
int fd = open("5.jpeg", O_WRONLY |O_CREAT|O_TRUNC,0666);
if (fd == -1)
{
perror("open");
return 1;
}
while (1)
{
char buf[1024] = {0};
int ret = recvfrom(udpfd, buf, sizeof(buf), 0, NULL, NULL);
//buf[ret] = '\0';
if (strcmp(buf, "aaa") == 0)//不执行的原因:recvfrom读取后没有\0
{
printf("%s\n",buf);
break;
}
write(fd, buf, ret);
bzero(buf, sizeof(buf));
strcpy(buf, "go on");
sendto(udpfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
//bzero(buf, sizeof(buf));
}
close(fd);
close(udpfd);
return 0;
}
我们可以使用udp实现一个聊天室,因为收发消息是并发的,所以这里我们在客户端和服务端都创建两个线程,一个负责收消息,一个负责发消息。
a:
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
typedef struct ip_info{
int fd;
struct sockaddr *addr;
socklen_t addr_len;
}info;
//接受信息
void* func1(void* arg){
struct ip_info ip = *(struct ip_info*)arg;
while (1)
{
char buf[1024] = {0};
recvfrom(ip.fd, buf, sizeof(buf), 0, ip.addr, &(ip.addr_len));
if (strcmp(buf, "quit\n") == 0)
{
printf("退出\n");
exit(0);
}
printf("from B:%s\n",buf);
}
return NULL;
}
//发送信息
void* func2(void* arg){
struct ip_info ip = *(struct ip_info*)arg;
while (1)
{
char buf[1024] = {0};
printf("to B\n");
fgets(buf, sizeof(buf), stdin);
sendto(ip.fd, buf, sizeof(buf), 0, ip.addr, ip.addr_len);
if (strcmp(buf, "quit\n") == 0)
{
printf("退出\n");
exit(0);
}
}
return NULL;
}
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET,SOCK_DGRAM,0);
if (udpfd == -1)
{
perror("socket");
return 1;
}
// 2. 给套接字 绑定ip ,端口号
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser, cli; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("192.168.31.125");
//套接字绑定地址结构体 IP地址和端口号
int ret = bind(udpfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
pthread_t tid1;
pthread_t tid2;
//初始化ip结构体
struct ip_info ip;
ip.fd = udpfd;
ip.addr = (SA)&cli;
ip.addr_len = len;
char buf[1024] = {0};
//得到客户端的 地址
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
printf("%s\n",buf);
pthread_create(&tid1, NULL, func1, &ip);
pthread_create(&tid2, NULL, func2, &ip);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
b端:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
typedef struct sockaddr *(SA);
typedef struct ip_info{
int fd;
struct sockaddr *addr;
socklen_t addr_len;
}info;
//接受信息
void* func1(void* arg){
struct ip_info ip = *(struct ip_info*)arg;
while (1)
{
char buf[1024] = {0};
recvfrom(ip.fd, buf, sizeof(buf), 0, ip.addr, &(ip.addr_len));
if (strcmp(buf, "quit\n") == 0)
{
printf("退出\n");
exit(0);
}
printf("from A:%s\n",buf);
}
return NULL;
}
//发送信息
void* func2(void* arg){
struct ip_info ip = *(struct ip_info*)arg;
while (1)
{
char buf[1024] = {0};
printf("to A\n");
fgets(buf, sizeof(buf), stdin);
sendto(ip.fd, buf, sizeof(buf), 0, ip.addr, ip.addr_len);
if (strcmp(buf, "quit\n") == 0)
{
printf("退出\n");
exit(0);
}
}
return NULL;
}
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("192.168.31.125");
socklen_t len = sizeof(ser);
pthread_t tid1;
pthread_t tid2;
//初始化ip结构体
struct ip_info ip;
ip.fd = udpfd;
ip.addr = (SA)&ser;
ip.addr_len = len;
char buf[1024] = "咱们开始聊天吧";
//告知服务端自己的 地址
sendto(udpfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
pthread_create(&tid1, NULL, func1, &ip);
pthread_create(&tid2, NULL, func2, &ip);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(udpfd);
return 0;
}
TCP 传输控制协议
1. 特征
有链接 ,在正式通信之前,需要先确定一条链路。在通信过程中,需要一致保持。直到其中
一方需要断开为止。
可靠的传输。在发送数据过程,数据都能到达对发。如果出现错误,可以收发函数的返回值判
断。
应答机制,超时重传机制。拥塞控制。
因为需要可靠传输,就需要维护链路的状态信息,网络开销,网络延迟大
全双工 ,同一时刻,可以同时收发
流式套接字
2. 编程步骤
如果为NULL,表示不关心对方地址。
addrlen 对方地址信息结构体大小。
如果对方地址是NULL,则该值也为NULL。
返回值:成功 接收到的数据长度
失败 -1;
流式套接字
1 ,发送的次数和接收的次不需要对应
2 , 数据与数据是连续。
3. 顺序(tcp), udp不保证顺序到达
4.有读阻塞,写阻塞
64K
3.三次握手,四次挥手过程

4.黏包问题
发送方多次发送数据,
接收方无法正常解析数据。
解决方案:
设置结束标志
发送固定大小,struct
自定义协议 ,变长
5.通信套接字和监听套接字
客户端刚开始连接服务器的,先找listfd(监听套接字,IP+port) .一旦成功建立连接,服务端会产生一个
新的套接字conn(通信套接字)。对于服务端而言,如果需要给第一个客户端通信的话,给 下图的4 发数据...

6.相关函数
int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符
参数:domain 地址族,PF_INET == AF_INET ==>互联网程序
PF_UNIX == AF_UNIX ==>单机程序
type 套接字类型:SOCK_STREAM 流式套接字 ===》TCP
SOCK_DGRAM 用户数据报套接字===>UDP
SOCK_RAW 原始套接字 ===》IP
protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id
失败 -1;
2、int bind(int sockfd, struct sockaddr *my_addr,
socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将参数1相关
的文件描述符文件与参数2 指定的接口地址关联,
用于从该接口接受数据。
如果该函数在客户端调用,则表示要将数据从
参数1所在的描述符中取出并从参数2所在的接口
设备上发送出去。
3. int listen(int sockfd, int backlog);
功能:在参数1所在的套接字id上监听等待链接。
参数:sockfd 套接字id
backlog 允许链接的个数。
返回值:成功 0
失败 -1
4、int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
功能:从已经监听到的队列中取出有效的客户端链接并
接入到当前程序。
参数:sockfd 套接字id
addr 如果该值为NULL ,表示不论客户端是谁都接入。
如果要获取客户端信息,则事先定义变量
并传入变量地址,函数执行完毕将会将客户端
信息存储到该变量中。
addrlen: 参数2的长度,如果参数2为NULL,则该值也为NULL;
如果参数不是NULL,&len;
一定要写成len = sizeof(struct sockaddr);
返回值:成功 返回一个用于通信的新套接字id;
从该代码之后所有通信都基于该id
失败 -1;
5.ssize_t recv(int sockfd, void *buf, size_t len,
int flags);
功能:从指定的sockfd套接字中以flags方式获取长度
为len字节的数据到指定的buff内存中。
参数:sockfd
如果服务器则是accept的返回值的新fd
如果客户端则是socket的返回值旧fd
buff 用来存储数据的本地内存,一般是数组或者
动态内存。
len 要获取的数据长度
flags 获取数据的方式,0 表示阻塞接受。
返回值:成功 表示接受的数据长度,一般小于等于len
失败 -1;
6. int send(int sockfd, const void *msg,
size_t len, int flags);
功能:从msg所在的内存中获取长度为len的数据以flags
方式写入到sockfd对应的套接字中。
参数:sockfd:
如果是服务器则是accept的返回值新fd
如果是客户端则是sockfd的返回值旧fd
7. int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:该函数固定有客户端使用,表示从当前主机向目标
主机发起链接请求。
参数:sockfd 本地socket创建的套接子id
addr 远程目标主机的地址信息。
addrlen: 参数2的长度。
返回值:成功 0
失败 -1;
数据的封包拆包

协议头
mac 头
以太网V2 MAC帧

ip头 大小:20byte
ttl 生命周期 ,是一个整形的数字(一般初值64)。 每过一个网络节点,就减1
ip flag ,D,是否允许分片 M,是否是最后一片。
tcp 头 大小:20byte
tcp flag , u 表示本帧数据中有紧急数据
a,表示本帧数据中有应答数据
p,表示本帧数据中有应用层数据的传输
r,表示本帧数据中有reset 操作
s,表示本帧数据中有三次握手
f,表示本帧数据中有四次挥手

udp 头 大小:8byte
我们可以写一个TCP的小示例
server端:
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
// 1 打开网络设备,获得文件描述(套接字)
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1)
{
perror("listfd");
return 1;
}
struct sockaddr_in ser,cli;
//清空 结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
// 2 给套接字绑定 ip +port ,方便客户端找到服务器
int ret = bind(listfd, (SA)&ser,sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
// 3 进入监听状态 (套接字进入可以被链接的状态)
// 参数2 ,进行三次握手的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
// 4 建立链接 (触发三次握手)
// 通信套接字,表示从服务端来看,conn 是客户端的套接字
int conn = accept(listfd, (SA)&cli,&len);
if (conn == -1)
{
perror("accept");
return 1;
}
while (1)
{
char buf[1024] = {0};
// 5 接收数据
// 返回值 >0 实际收到的字节数 ==0 对方断开 -1 错误
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
perror("recv");
break;
}
printf("from client:%s\n",buf);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf,ctime(&tm));
// 6 发送数据
send(conn, buf, strlen(buf), 0);
}
close(listfd);
close(conn);
return 0;
}
client端:
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
while(1){
char buf[1024] = "hello, this is a tcp test";
send(sockfd, buf, strlen(buf), 0);
bzero(buf, sizeof(buf));
int rd_ret = recv(sockfd, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
break;
}
printf("from server:%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
我们使用TCP同样可以实现文件发送
server端发文件:
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1)
{
perror("listfd");
return 1;
}
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
listen(listfd, 3);
socklen_t len = sizeof(cli);
int conn = accept(listfd, (SA)&cli,&len);
if (conn == -1)
{
perror("conn");
return 1;
}
int fd = open("1.jpeg", O_RDONLY);
if (fd == -1)
{
perror("open");
return 1;
}
while (1)
{
char buf[1024] = {0};
int rd_ret = read(fd, buf, sizeof(buf));
if (rd_ret <= 0)
{
break;
}
send(conn, buf, rd_ret, 0);
bzero(buf, sizeof(buf));
//会阻塞,可以等待接收端写入文件
recv(conn, buf, sizeof(buf), 0);
}
close(listfd);
close(conn);
close(fd);
return 0;
}
client端:
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
int fd = open("3.jpeg", O_WRONLY|O_CREAT|O_TRUNC,0666);
if (fd == -1)
{
perror("open");
return 1;
}
while(1){
char buf[1024] = {0};
int rd_ret = recv(sockfd, buf, sizeof(buf), 0);
if (rd_ret <= 0)//此处为退出方式,当对方断开链接,收发函数的返回值改变,可以判断,程序退出
{
break;
}
write(fd, buf, rd_ret);
strcpy(buf, "continue");
//发送消息给服务端,告诉服务端继续发文件
send(sockfd, buf, strlen(buf), 0);
}
close(sockfd);
return 0;
}
我们也可以使用TCP实现一个聊天室
a端:
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
//发
void* th1(void* arg){
while (1)
{
int fd = *(int*)arg;
char buf[1024] = {0};
printf("to B:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
send(fd, buf, strlen(buf), 0);
if (strcmp(buf, "#quit\n") == 0)
{
close(fd);
exit(0);
}
}
return NULL;
}
//收
void* th2(void* arg){
while (1)
{
int fd = *(int*)arg;
char buf[1024] = {0};
//printf("from B:\n");
recv(fd, buf, sizeof(buf), 0);
if (strcmp(buf, "#quit\n") == 0)
{
close(fd);
exit(0);
}
printf("from B:%s\n",buf);
}
return NULL;
}
int main(int argc, char **argv)
{
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1)
{
perror("listfd");
return 1;
}
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
listen(listfd, 3);
socklen_t len = sizeof(cli);
int conn = accept(listfd, (SA)&cli,&len);
if (conn == -1)
{
perror("conn");
return 1;
}
pthread_t tid1,tid2;
pthread_create(&tid1, NULL,th1, &conn);
pthread_create(&tid2, NULL,th2, &conn);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
b端:
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
//发
void* th1(void* arg){
while (1)
{
int fd = *(int*)arg;
char buf[1024] = {0};
printf("to A:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
send(fd, buf, strlen(buf), 0);
if (strcmp(buf, "#quit\n") == 0)
{
close(fd);
exit(0);
}
}
return NULL;
}
//收
void* th2(void* arg){
while (1)
{
int fd = *(int*)arg;
char buf[1024] = {0};
//printf("from A:\n");
recv(fd, buf, sizeof(buf), 0);
if (strcmp(buf, "#quit\n") == 0)
{
close(fd);
exit(0);
}
printf("from A:%s\n",buf);
}
return NULL;
}
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
pthread_t tid1,tid2;
pthread_create(&tid1, NULL,th1, &sockfd);
pthread_create(&tid2, NULL,th2, &sockfd);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
与UDP实现的思路一样,由于收发消息是并发执行的,所以这里定义两个线程一个负责收,一个负责发。
TCP模拟查字典功能,客户端向服务端发送单词,服务端负责查单词的意思并发送给客户端打印。
server端
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1)
{
perror("listfd");
return 1;
}
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
listen(listfd, 3);
socklen_t len = sizeof(cli);
int conn = accept(listfd, (SA)&cli,&len);
if (conn == -1)
{
perror("conn");
return 1;
}
int fd = open("dict.txt", O_RDONLY);
if (fd == -1)
{
perror("open");
return 1;
}
FILE* fp = fdopen(fd, "r");
if (fp == NULL)
{
perror("fdopen");
return 1;
}
while (1)
{
int flag = 0;
char buf[1024] = {0};
int ret = recv(conn, buf, sizeof(buf), 0);
buf[ret-1] = '\0';
if (strcmp(buf, "#quit\n") == 0)
{
close(conn);
close(fd);
close(listfd);
fclose(fp);
exit(0);
}
char s[1024]= {0};
rewind(fp);
while (fgets(s, sizeof(s), fp) != NULL)
{
int i = 0;
char* dic[1024];
dic[0] = strtok(s," ");
dic[1] = strtok(NULL,"\n");
if (strcmp(buf, dic[0]) == 0)
{
send(conn, dic[1], strlen(dic[1]), 0);
flag = 1;
break;
}
}
if (flag == 0)
{
char message[1024] = "没找到";
send(conn, message, strlen(message), 0);
}
}
close(conn);
close(listfd);
return 0;
}
client端:
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
while (1)
{
char buf[1024] = {0};
printf("请输出:\n");
fgets(buf, sizeof(buf), stdin);
send(sockfd, buf, strlen(buf), 0);
if (strcmp(buf, "#quit\n") == 0)
{
close(sockfd);
exit(0);
}
bzero(buf, sizeof(buf));
recv(sockfd, buf, sizeof(buf), 0);
printf("%s\n", buf);
}
return 0;
}
wireshark
网络抓包工具,网络调试,协议分析。可以看到网络设备,底层收发到的字节。
1.sudo wireshark
2.选择网络设备
any ,获取所有网络设备的数据(默认)
lo: 本地回环, 获得本机发送或接收的数据
ens33 ,获得通过外网收发的数据

3.设置过滤条件
通过 端口设备 过滤条件 tcp.port == 80 || udp.port == 80
通过ip地址 设置过滤条件 ip.addr == 192.0.2.1
条件之间可以使用 &&(同时满足) ||(满足一个)
界面介绍
最上面的窗口 wireshark 获得的 报文
中间窗口 是tcp/ip 四层模型
最下面窗口 一帧数据对应的字节

http 超文本传输协议 (网页)
是一个应用层的协议。主要用于浏览器和服务器之间的交互。
url , 统一资源定位符。在整个互联网唯一表示一个资源(网页,照片,电影。。。)
http 超文本传输协议, 是基于tcp的。 默认端口号 80 。https(加密版本的http) ,443
html 超文本标记语言, 编写网页的一类编程语言。 经过浏览器的解析,最终显示在浏览器中。
http的交互过程
4 步。
建立TCP连接
发送http 请求报文
接收http响应报文和数据
断开连接
http的请求报文的格式

GET / HTTP/1.1\r\n 请求行
期望接收的数据的类型
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,imag
e/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n
期望接收数据的编码
Accept-Encoding: gzip, deflate\r\n
期望接收数据语言
Accept-Language: zh-CN,zh;q=0.9
连接方式 : 长连接
Connection: keep-alive
请求的主机
Host: news.sohu.com
用户代理
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/145.0.0.0 Safari/537.36\r\n\r\n
定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。 io事件(读写)的通知机制。
IO模型
1、阻塞IO 默认
2、非阻塞IO EAGAIN 忙等待 errno 需要掌握设置方法 fctnl
3、信号驱动IO SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5, IO多路复用 select、poll、epoll
下面是用TCP实现http的示例,实现了城市天气预报的功能。通过TCP模拟浏览器向服务器发送http请求,即将http请求头通过TCP的方式放松给服务器。每次将城市名字拼接进请求头中。
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<ctype.h>
typedef struct sockaddr *(SA);
char city[10] = "成都";
char* city_encode(const char* city){
size_t len = strlen(city);
// 最坏情况:每个字节都可能变成 %XX(3 个字符),加上结尾的 '\0'
char *encoded = malloc(len * 3 + 1);
if (encoded == NULL) {
return NULL; // 内存分配失败
}
const char *src = city; // 源指针
char *dst = encoded; // 目标指针
while (*src != '\0') {
unsigned char c = (unsigned char)(*src); // 转为无符号,避免 isalnum 处理负值
// 其他字符(包括中文的每个字节)编码为 %XX
sprintf(dst, "%%%02X", c); // 写入 "%" + 两位大写十六进制
dst += 3; // 跳过刚写入的 3 个字符
src++;
}
*dst = '\0'; // 添加字符串结束符
return encoded;
}
void set_city(){
scanf("%s",city);
}
void weather_live(int sockfd,const char* encode){
char* buff[10] = {NULL};
char request[1024] = {0};
char* head = "GET /?app=weather.today&cityNm=";
char* tale = "&appkey=78670&sign=7859c8f61b3079f734671b484cd60b04&format=json HTTP/1.1\r\n";
sprintf(request,"%s%s%s", head,encode,tale);
buff[0] = request;
buff[1] = "Host: api.k780.com\r\n";
buff[2] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n";
buff[3] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n";
buff[4] = "Accept-Language: en-US,en;q=0.5\r\n";
buff[5] = "Accept-Encoding: gzip, deflate\r\n";
buff[6] = "Connection: keep-alive\r\n";
buff[7] = "Upgrade-Insecure-Requests: 1\r\n\r\n";
char buf[2048] = {0};
for (int i = 0; i < 8; i++)
{
send(sockfd, buff[i], strlen(buff[i]), 0);
}
int ret = recv(sockfd, buf, sizeof(buf), 0);
//printf("%s\n",buf);
char *days = strstr(buf, "days");
char *week = strstr(days, "week");
char *citynm = strstr(week, "citynm");
char *temp = strstr(citynm, "temperature");
char *wea = strstr(temp, "weather");
char *end = 0;
days += 7;
end = strchr(days, '"');
*end = '\0';
week += 7;
end = strchr(week, '"');
*end = '\0';
citynm+=9;
end = strchr(citynm, '"');
*end = '\0';
temp+=14;
end = strchr(temp, '"');
*end = '\0';
wea+=10;
end = strchr(wea, '"');
*end = '\0';
printf("%s:%s:%s:%s:%s\n",days,week,citynm,temp,wea);
}
void life_index(int sockfd,const char* encode){
char* buff[10] = {NULL};
char request[1024] = {0};
char* head = "GET /?app=weather.lifeindex&cityNm=";
char* tale = "&appkey=78670&sign=7859c8f61b3079f734671b484cd60b04&format=json HTTP/1.1\r\n";
sprintf(request,"%s%s%s", head,encode,tale);
buff[0] = request;
buff[1] = "Host: api.k780.com\r\n";
buff[2] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n";
buff[3] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n";
buff[4] = "Accept-Language: en-US,en;q=0.5\r\n";
buff[5] = "Accept-Encoding: gzip, deflate\r\n";
buff[6] = "Connection: keep-alive\r\n";
buff[7] = "Upgrade-Insecure-Requests: 1\r\n\r\n";
char buf[5120] = {0};
int i = 0;
for (i = 0; i < 8; i++)
{
send(sockfd, buff[i], strlen(buff[i]), 0);
}
int ret = recv(sockfd, buf, sizeof(buf), 0);
char *p = buf;
for (i = 0; i < 7; i++)
{
char *days = strstr(p, "days");
char *week = strstr(days, "week_1");
char *citynm = strstr(week, "citynm");
char *lifeindex_uv_typenm = strstr(citynm, "lifeindex_uv_typenm");
char *lifeindex_uv_attr = strstr(lifeindex_uv_typenm, "lifeindex_uv_attr");
char *lifeindex_uv_dese = strstr(lifeindex_uv_attr, "lifeindex_uv_dese");
char *lifeindex_xc_typenm = strstr(lifeindex_uv_dese, "lifeindex_xc_typenm");
char *lifeindex_xc_dese = strstr(lifeindex_xc_typenm, "lifeindex_xc_dese");
char *end = 0;
days += 7;
end = strchr(days, '"');
*end = '\0';
week += 9;
end = strchr(week, '"');
*end = '\0';
citynm+=9;
end = strchr(citynm, '"');
*end = '\0';
lifeindex_uv_typenm+=22;
end = strchr(lifeindex_uv_typenm, '"');
*end = '\0';
lifeindex_uv_attr+=20;
end = strchr(lifeindex_uv_attr, '"');
*end = '\0';
lifeindex_uv_dese+=20;
end = strchr(lifeindex_uv_dese, '"');
*end = '\0';
lifeindex_xc_typenm+=22;
end = strchr(lifeindex_xc_typenm, '"');
*end = '\0';
lifeindex_xc_dese+=20;
end = strchr(lifeindex_xc_dese, '"');
*end = '\0';
printf("%s:%s:%s %s:%s %s:%s:%s\n",days,week,citynm,lifeindex_uv_typenm,lifeindex_uv_attr,lifeindex_uv_dese,lifeindex_xc_typenm,lifeindex_xc_dese);
p = end + 1;
}
}
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(80);
ser.sin_addr.s_addr = inet_addr("8.129.233.227");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
//printf("%s\n",city_encode("成都"));
while (1)
{
printf("1 设置城市\n");
printf("2 实时天气\n");
printf("3 生活指数\n");
printf("4 退出\n");
int choice;
scanf("%d",&choice);
switch (choice)
{
case 1:
set_city();
break;
case 2:
weather_live(sockfd,city_encode(city));
break;
case 3:
life_index(sockfd,city_encode(city));
break;
case 4:
exit(0);
break;
default:
break;
}
}
close(sockfd);
return 0;
}
多路io
IO多路复用
作用:
应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。
单个执行体(单进程,单线程)中,需要检测多个阻塞设备。
select 使用的步骤
GET / HTTP/1.1\r\n 请求行
期望接收的数据的类型
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,imag
e/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n
期望接收数据的编码
Accept-Encoding: gzip, deflate\r\n
期望接收数据语言
Accept-Language: zh-CN,zh;q=0.9
连接方式 : 长连接
Connection: keep-alive
请求的主机
Host: news.sohu.com
用户代理
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/145.0.0.0 Safari/537.36\r\n\r\n
定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。 io事件(读写)的通知机制。
IO模型
1、阻塞IO 默认
2、非阻塞IO EAGAIN 忙等待 errno 需要掌握设置方法 fctnl
3、信号驱动IO SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5, IO多路复用 select、poll、epoll
select 使用的步骤
相关函数
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
功能:完成指定描述符集合中有效描述符的动态检测。
该函数具有阻塞等待功能,在函数执行完毕后
目标测试集合中将只保留最后有数据的描述符。
参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
readfds 只读描述符集
writefds 只写描述符集
exceptfds 异常描述符集
以上三个参数都是 fd_set * 的描述符集合类型
timeout 检测超时 如果是NULL表示一直检测不超时 。
返回值:超时 0
失败 -1
成功 >0
辅助函数
为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。
int FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,
如果在则返回真,否则返回假。
void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。
void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。
注意事项:
1 . select 函数会修改readfds 集合。如果是循环调用select ,需要清除标志位。
2. nfds 进程中最大文件描述的那个整数。 可以直接写1024;
3. select如果是超时版本,在调用select前,都需要重新设置时间。
4 select 集合最大检测 1024个;
以下是TCP通过select实现的多客户端连接
server端:
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include<sys/select.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
// 1 打开网络设备,获得文件描述(套接字)
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser,cli;
//清空 结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
// 2 给套接字绑定 ip +port ,方便客户端找到服务器
int ret = bind(listfd, (SA)&ser,sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
// 3 进入监听状态 (套接字进入可以被链接的状态)
// 参数2 ,进行三次握手的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
// sel_1 create set
fd_set rd_set,tmp_set;
// sel2_ add fd
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
FD_SET(listfd, &tmp_set);
int maxfd = listfd;
while (1)
{
// sel5 清除sel标志位
rd_set = tmp_set;
// sel3 wait event 等待事件到来
int sel_ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);
int i = 0;
for (i = 0; i < maxfd + 1; i++)
{
if (FD_ISSET(i, &rd_set) && i == listfd){//当客户端想建立连接时走这
// 4 建立链接 (触发三次握手)
// 通信套接字,表示从服务端来看,conn 是客户端的套接字
int conn = accept(listfd, (SA)&cli,&len);
if (conn == -1)//客户端想断开连接
{
perror("accept");
close(conn);
continue;
}
FD_SET(conn, &tmp_set);
//每次需要更新最大的fd号,传进循环条件
maxfd = conn > maxfd ? conn : maxfd;
}
if (FD_ISSET(i, &rd_set) && i != listfd){
int conn = i;
char buf[1024] = {0};
// 5 接收数据
// 返回值 >0 实际收到的字节数 ==0 对方断开 -1 错误
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
printf("cli offline\n");
FD_CLR(conn, &tmp_set);
close(conn);
continue;;
}
printf("from client:%s\n",buf);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf,ctime(&tm));
// 6 发送数据
send(conn, buf, strlen(buf), 0);
}
}
}
close(listfd);
//close(conn);
return 0;
}
client端:
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("connect");
return 1;
}
while(1){
char buf[1024] = "hello, this is a tcp test";
send(sockfd, buf, strlen(buf), 0);
bzero(buf, sizeof(buf));
int rd_ret = recv(sockfd, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
break;
}
printf("from server:%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
以下是UDP通过select实现聊天室的具体过程。
a端:
#include <sys/select.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET,SOCK_DGRAM,0);
if (udpfd == -1)
{
perror("socket");
return 1;
}
// 2. 给套接字 绑定ip ,端口号
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser, cli; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = INADDR_ANY;
int ret = bind(udpfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
char buf[1024] = {0};
//得到客户端的 地址
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
printf("%s\n",buf);
fd_set rd_set,tmp_set;
// sel2_ add fd
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
FD_SET(0, &tmp_set);
FD_SET(udpfd, &tmp_set);
int maxfd = udpfd;
while (1)
{
rd_set = tmp_set;
select(maxfd + 1, &rd_set, NULL, NULL, NULL);
int i = 0;
for (i = 0; i < maxfd + 1; i++)
{
char buf[1024] = {0};
if (FD_ISSET(i, &rd_set) && i == udpfd)//套接字走这
{
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
if (strcmp(buf, "quit\n") == 0)
{
printf("接收退出\n");
close(udpfd);
exit(0);
}
printf("from B:%s\n",buf);
}
if (FD_ISSET(i, &rd_set) && i == 0)
{
printf("to B:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
sendto(udpfd, buf, sizeof(buf), 0, (SA)&cli, len);
if (strcmp(buf, "quit\n") == 0)
{
printf("发送退出\n");
close(udpfd);
exit(0);
}
}
}
}
return 0;
}
b端:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t len = sizeof(ser);
char buf[1024] = "咱们开始聊天吧";
//告知服务端自己的 地址
sendto(udpfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
fd_set rd_set,tmp_set;
// sel2_ add fd
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
FD_SET(0, &tmp_set);
FD_SET(udpfd, &tmp_set);
int maxfd = udpfd;
while (1)
{
rd_set = tmp_set;
select(maxfd + 1, &rd_set, NULL, NULL, NULL);
int i = 0;
for (i = 0; i < maxfd + 1; i++)
{
char buf[1024] = {0};
if (FD_ISSET(i, &rd_set) && i == udpfd)//套接字走这
{
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&ser, &len);
if (strcmp(buf, "quit\n") == 0)
{
printf("接收退出\n");
close(udpfd);
exit(0);
}
printf("from B:%s\n",buf);
}
if (FD_ISSET(i, &rd_set) && i == 0)
{
printf("to B:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
sendto(udpfd, buf, sizeof(buf), 0, (SA)&ser, len);
if (strcmp(buf, "quit\n") == 0)
{
printf("发送退出\n");
close(udpfd);
exit(0);
}
}
}
}
close(udpfd);
return 0;
}
epoll
epoll 使用步骤
相关函数
int epoll_create(int size);
功能:创建一个集合(二叉树) ,存续需要被检测的文件描述符。
参数:size ,指定集合可以储存文件描述符的个数。
返回值:
成功 >0 表示对应二叉树的文件描述符( 用于表示某个资源)
失败 -1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对epoll 集合进行相关的操作,具体操作由op 指定。
参数:
epfd,需要被操作的集合
op :
EPOLL_CTL_ADD ,向集合中添加文件描述符
EPOLL_CTL_DEL ,向集合中删除文件描述符
fd ,需要 添加或删除的那个文件描述符
event , 结构体 ,
struct epoll_event {
uint32_t events; EPOLLIN(read)/EPOLLOUT(write)
特征:
服务器的并发模型
进程版本
epoll_data_t data; 用户自定义的数据,查找文件描述符的时候会使用到
};
返回值:
成功 ==0
失败 -1
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int
timeout);
功能: 等待io事件(可能是读,写,错误)的到来。
参数:
epfd,需要被检测的集合
events: 这个是输出的集合,当有io设备准备就绪(可读,可写)。系统会把这些设备对应的文件描述符,复
制到这个集合中。
maxevents, 在一次检测中,可以同时复制到events 中的 数量。
timeout 是超时值的设置。 -1 阻塞
5000 == 5s
0 非阻塞
返回值:
成功 >0
超时 ==0
失败 -1
1 . 存储文件描述符集合的个数,不再受1024 限制
2 .检测机制是 主动上报 ,不会随着文件描述的增多,而效率下降
3 。使用共享内存,避免集合在用户层和内核层 多次复制。
4. 当epoll_wait 返回后,准备就绪的文件描述符都储存在 events 集合中,相对容易查找。
1 .僵尸进程的回收(信号的方式)
2. 文件描述符的回收 listfd conn ,对于父子进程的差异
TCP同样可以通过epoll实现多端连接
server端:
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include<sys/epoll.h>
typedef struct sockaddr *(SA);
int add_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
if (ret == -1)
{
perror("add_fd");
return ret;
}
return 0;
}
int del_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
if (ret == -1)
{
perror("add_fd");
}
return 0;
}
int main(int argc, char **argv)
{
// 1 打开网络设备,获得文件描述(套接字)
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser, cli;
//清空 结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50001);
ser.sin_addr.s_addr = INADDR_ANY;
// 2 给套接字绑定 ip +port ,方便客户端找到服务器
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("bind");
return 1;
}
// 3 进入监听状态 (套接字进入可以被链接的状态)
// 参数2 ,进行三次握手的排队数
listen(listfd, 3);
socklen_t len = sizeof(cli);
struct epoll_event rev[10];
int epfd = epoll_create(10);
if (epfd == -1)
{
perror("epoll_create");
return 1;
}
add_fd(epfd, listfd);
while(1){
int ep_ret = epoll_wait(epfd, rev, 10, -1);
int i = 0;
for (i = 0; i < ep_ret; i++)
{
if (rev[i].data.fd == listfd)// 三次握手
{
// 4 建立链接 (触发三次握手)
// 通信套接字,表示从服务端来看,conn 是客户端的套接字
int conn = accept(listfd, (SA)&cli, &len);
if (conn == -1)
{
perror("conn");
close(conn);
continue;
}
add_fd(epfd, conn);
}
else
{
char buf[1024] = {0};
int conn = rev[i].data.fd;
// 5 接收数据
// 返回值 >0 实际收到的字节数 ==0 对方断开 -1 错误
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret == -1)
{
printf("cli offline");
del_fd(epfd, conn);
close(conn);
continue;
}
// 5.5 处理数据
printf("cli:%s\n", buf);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf, ctime(&tm));
// 6 发送数据
send(conn, buf, strlen(buf), 0);
}
}
}
close(listfd);
return 0;
}
client端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
// 1 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser;
//清空 结构体
bzero(&ser, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50001);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
// 2 ,主动连接服务器
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("connect");
return 1;
}
while (1)
{
char buf[512]="hello,this is tcp test";
// 3. 发送数据 返回值 >0 实际发送的字节数 ==0 网络状态不好,没有发出数据 -1错误
send(sockfd,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
// 4. 接收数据 >0 实际接收的字节数 ==0 表示断开 -1 错误
int rd_ret = recv(sockfd,buf,sizeof(buf),0);
if(rd_ret<=0)
{
break;
}
printf("from ser:%s\n",buf);
sleep(1);
}
// 5.断开连接
close(sockfd);
return 0;
}
同样用UDP通过epoll实现聊天室:
a端:
#include <sys/types.h> /* See NOTES */
#include <pthread.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include<sys/epoll.h>
typedef struct sockaddr* (SA);
int add_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
if (ret == -1)
{
perror("add_fd");
return ret;
}
return 0;
}
int del_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
if (ret == -1)
{
perror("add_fd");
}
return 0;
}
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET,SOCK_DGRAM,0);
if (udpfd == -1)
{
perror("socket");
return 1;
}
// 2. 给套接字 绑定ip ,端口号
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser, cli; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = INADDR_ANY;
//套接字绑定地址结构体 IP地址和端口号
int ret = bind(udpfd, (SA)&ser, sizeof(ser));
if (ret == -1)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
struct epoll_event rev[2];
int epfd = epoll_create(2);
if (epfd == -1)
{
perror("epoll_create");
return 1;
}
add_fd(epfd, udpfd);
add_fd(epfd, 0);
char buf[1024] = {0};
//得到客户端的 地址
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
printf("%s\n",buf);
while (1)
{
int ep_ret = epoll_wait(epfd, rev, 2, -1);
int i = 0;
for (i = 0; i < ep_ret; i++)
{
char buf[1024] = {0};
if (rev[i].data.fd == udpfd)
{
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&cli, &len);
if (strcmp(buf, "quit\n") == 0)
{
del_fd(epfd, udpfd);
close(udpfd);
exit(0);
}
printf("from B:%s\n",buf);
}
else
{
printf("to B:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
sendto(udpfd, buf, sizeof(buf), 0, (SA)&cli, len);
if (strcmp(buf, "quit\n") == 0)
{
del_fd(epfd, udpfd);
close(udpfd);
exit(0);
}
}
}
}
return 0;
}
b端:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
#include<sys/epoll.h>
typedef struct sockaddr *(SA);
int add_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
if (ret == -1)
{
perror("add_fd");
return ret;
}
return 0;
}
int del_fd(int epfd,int fd){
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
if (ret == -1)
{
perror("add_fd");
}
return 0;
}
int main(int argc, char **argv)
{
// 1. internet , udp, 默认协议
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udpfd)
{
perror("socket");
return 1;
}
// man 7 ip 查询 ipv4 的地址结构体
struct sockaddr_in ser; // 服务器的地址结构体, 客户端的地址结构体
bzero(&ser, sizeof(ser));
//指定网络类型
ser.sin_family = AF_INET; // ipv4
//初始化端口号,因为网络设备的大端存储的,所以需要转换一下
ser.sin_port = htons(50000); // host to net short 小端转大端
//初始化ip地址
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t len = sizeof(ser);
struct epoll_event rev[2];
int epfd = epoll_create(2);
if (epfd == -1)
{
perror("epoll_create");
return 1;
}
add_fd(epfd, udpfd);
add_fd(epfd, 0);
char buf[1024] = "咱们开始聊天吧";
//告知服务端自己的 地址
sendto(udpfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
while (1)
{
int ep_ret = epoll_wait(epfd, rev, 2, -1);
int i = 0;
for (i = 0; i < ep_ret; i++)
{
char buf[1024] = {0};
if (rev[i].data.fd == udpfd)
{
recvfrom(udpfd, buf, sizeof(buf), 0, (SA)&ser, &len);
if (strcmp(buf, "quit\n") == 0)
{
del_fd(epfd, udpfd);
close(udpfd);
exit(0);
}
printf("from A:%s\n",buf);
}
else
{
printf("to A:");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
sendto(udpfd, buf, sizeof(buf), 0, (SA)&ser, len);
if (strcmp(buf, "quit\n") == 0)
{
del_fd(epfd, udpfd);
close(udpfd);
exit(0);
}
}
}
}
close(udpfd);
return 0;
}
服务器的并发模型
进程版本
/**
* @file 01ser.c 进程版本的并发 tcp 服务器
1 .僵尸进程的回收
2. 文件描述符的回收 listfd conn ,对于父子进程的差异
* @author yashiro (rage_yas@hotmail.com)
* @brief
* @version 0.1
* @date 2026-03-09
*
* @copyright Copyright (c) 2026
*
*/
#include <endian.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <sys/wait.h>
#include <signal.h>
typedef struct sockaddr *(SA);
void handler(int num){
wait(NULL);
}
int main(int argc, char **argv)
{
signal(SIGCHLD, handler);//为了回收时不阻塞,用信号来回收子进程
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser, cli;
//清空 结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
// 2 给套接字绑定 ip +port ,方便客户端找到服务器
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("bind");
return 1;
}
// 3 进入监听状态 (套接字进入可以被链接的状态)
// 参数2 ,进行三次握手的排队数
listen(listfd, 3);
socklen_t len = sizeof(cli);
while (1)
{
int conn = accept(listfd, (SA)&cli, &len);
if (conn == -1)
{
perror("conn");
close(conn);//没链接成功继续连
continue;
}
pid_t pid = fork();//一般情况下,死循环中的fork会导致锁死,但是这之前调用了accept,所以会先阻塞住。
if (pid > 0)
{
close(conn);//为了节省资源,父进程只负责三次握手,只需要用到listfd,所以关闭通信套接字
}
else if (pid == 0)
{
close(listfd);//为了节省资源,子进程只负责通信,只需要用到conn,所以关闭监听套接字
while (1) {
char buf[1024] = {0};
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
perror("cli offline\n");
exit(0);
}
// 5.5 处理数据
printf("cli:%s\n", buf);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf, ctime(&tm));
// 6 发送数据
send(conn, buf, strlen(buf), 0);
}
}
else
{
perror("fork");
continue;
}
}
close(listfd);
return 0;
}
线程版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct sockaddr*(SA);
sem_t sem_arg;
void* th(void* arg){
int conn = *(int*)arg;
sem_post(&sem_arg);//在线程拿到conn后释放信号量,保证同步操作
// 分离属性, 当线程结束后,栈区自动由系统回收
pthread_detach(pthread_self());//设置分离属性
while (1)
{
char buf[512] = {0};
// 5 接收数据
// 返回值 >0 实际收到的字节数 ==0 对方断开 -1 错误
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret <= 0)
{
printf("cli offline\n");
close(conn);
break;
}
// 5.5 处理数据
printf("cli:%s\n", buf);
time_t tm;
time(&tm);
sprintf(buf, "%s %s", buf, ctime(&tm));
// 6 发送数据
send(conn, buf, strlen(buf), 0);
}
}
int main(int argc, char **argv)
{
// 1 打开网络设备,获得文件描述(套接字)
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser, cli;
//清空 结构体
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = INADDR_ANY;
// 2 给套接字绑定 ip +port ,方便客户端找到服务器
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("bind");
return 1;
}
// 3 进入监听状态 (套接字进入可以被链接的状态)
// 参数2 ,进行三次握手的排队数
listen(listfd, 3);
socklen_t len = sizeof(cli);
sem_init(&sem_arg, 0, 0);//设初值为0
while (1)
{
int conn = accept(listfd, (SA)&cli, &len);
if (conn == -1)
{
perror("conn");
close(conn);
continue;
}
pthread_t tid;
pthread_create(&tid, NULL, th, &conn);
//这里不用pthread_join是因为,会阻塞在这,导致其他客户端无法链接进来,所以需要设置分离属性
//由于线程接受conn可能会被和上面accept的conn影响
//(conn可能用同一个栈空间,conn还没完成传给线程,accept又修改了conn的值)所以传入conn和accept应该是同步操作,需要信号量
// 确保 conn 参数一定在th现存中,被保存下来
sem_wait(&sem_arg);//因为信号量初值为0,线程中接收到conn后post,信号量变为1,这里的sem_wait是给accept服务的
}
sem_destroy(&sem_arg);
// 7 断开链接
close(listfd);
return 0;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)