网络编程
计算机网络基础
socket介绍
#include<stdio.h>
#include<arpa/inet.h>
/*
#include <arpa/inet.h>
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序
*/
int main(){
//转换端口
unsigned short a=0x0102;
unsigned short b=htons(a);
printf("a:%x b:%x\n",a,b);
//转换ip
char buf[4]={192,168,1,100};
int num=*(int *)buf;
int sum=htonl(num);
unsigned char *p=(char *)∑
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
return 0;
}
#include <arpa/inet.h>
#include<stdio.h>
#include<string.h>
/*
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面传出参数
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);// 将网络字节序的整数,转换成点分十进制的IP地址字符串
af:地址族: AF_INET AF_INET6
src: 要转换的ip的整数的地址
dst: 转换成IP地址字符串保存的地方
size:第三个参数的大小(数组的大小)
返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
*/
int main(){
char ip[]="192.168.10.1";
printf("%d %d %s\n",sizeof(ip),strlen(ip),ip);
//点分十进制转换为网络字节序整数
unsigned int num=0;
inet_pton(AF_INET,ip,&num);
unsigned char *p=(unsigned char*)#
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
//将网络字节序转换为点分十进制
char ip1[16]="";
const char *a=inet_ntop(AF_INET,&num,ip1,16);
printf("a%s\n",a);
printf("ip1%s\n",ip1);
return 0;
}
TCP通信流程
socker通信示例 server
/*
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int socket(int domain, int type, int protocol);创建一个套接字
- domain: 协议族 AF_INET : ipv4 \ AF_INET6 : ipv6 \ AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)
- type: 通信过程中使用的协议类型 SOCK_STREAM : 流式协议 \ SOCK_DGRAM : 报式协议
- protocol : 具体的一个协议。一般写0 SOCK_STREAM : 流式协议默认使用 TCP \ SOCK_DGRAM : 报式协议默认使用 UDP
- 返回值: 失败:-1 成功:返回文件描述符,操作的就是内核缓冲区。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 绑定,将fd 和本地的IP + 端口进行绑定(socket命名)
- sockfd : 通过socket函数得到的文件描述符
- addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息
- addrlen : 第二个参数结构体占的内存大小
int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn 监听这个socket上的连接
- sockfd : 通过socket()函数得到的文件描述符
- backlog : 未连接的和已经连接的和的最大值, 5
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
- sockfd : 用于监听的文件描述符
- addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
- addrlen : 指定第二个参数的对应的内存大小
- 返回值:成功 :用于通信的文件描述符 失败-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 客户端连接服务器
- sockfd : 用于通信的文件描述符
- addr : 客户端要连接的服务器的地址信息
- addrlen : 第二个参数的内存大小
- 返回值:成功 0, 失败 -1
ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据
*/
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(){
//创建监听的套接字
int listenFd=socket(AF_INET,SOCK_STREAM,0);
if(listenFd==-1){
perror("socket");
exit(0);
}
//绑定
struct sockaddr_in saddr;
saddr.sin_family=AF_INET;
inet_pton(AF_INET,"172.19.2.247",&saddr.sin_addr.s_addr);
saddr.sin_port=htons(9999);
int ret=bind(listenFd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret==-1){
perror("bind");
exit(0);
}
//监听
ret=listen(listenFd,8);
if(ret==-1){
perror("listen");
exit(0);
}
//接受客户端连接
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int cfd=accept(listenFd,(struct sockaddr *)&clientaddr,&len);
if(cfd==-1){
perror("bind");
exit(0);
}
//输出客户端的信息
char clientIP[16];
inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
unsigned short clientPOrt=ntohs(clientaddr.sin_port);
printf("ip %s port %d \n",clientIP,clientPOrt);
//获取客服端的信息
char recivebuf[1024]={0};
while (1)
{
ssize_t len1=read(cfd,recivebuf,sizeof(recivebuf));
if(len1==-1){
perror("read");
exit(-1);
}else if(len1>0){
printf("收到%s\n",recivebuf);
}else if(len1==0){
printf("客户端断开连接");
break;
}
//发送信息
char * s="我说话了我是服务器";
write(cfd,s,strlen(s));
}
close(listenFd);
close(cfd);
return 0;
}
socker通信示例 client
#include <arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
/*
// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
//创建套接字
int cfd=socket(AF_INET,SOCK_STREAM,0);
if (cfd==-1){
perror("socket");
exit(-1);
}
//连接服务器
struct sockaddr_in ser;
ser.sin_family=AF_INET;
const char src="172.19.2.247";
inet_pton(AF_INET,"172.19.2.247",&ser.sin_addr.s_addr);
ser.sin_port=htons(9999);
socklen_t len=sizeof(ser);
int ret=connect(cfd,(struct sockaddr *)&ser,len);
if (ret==-1){
perror("connet");
exit(-1);
}
char buf[1024]={0};
while (1)
{
//写数据
char *s="我告诉你a我是客户";
ret=write(cfd,s,strlen(s));
if (ret==-1){
perror("write");
exit(-1);
}
//读数据
ssize_t len1=read(cfd,buf,sizeof(buf));
if(len1==-1){
perror("read");
exit(-1);
}else if(len1>0){
printf("收到%s\n",buf);
}else if(len1==0){
printf("服务器丢失");
}
sleep(1);
}
close(cfd);
return 0;
}
TCP三次握手
TCP滑动窗口
mss: MaximumSegment Size 一次发送的最大数据量
win: 滑动窗口
1.客户端向服务器发起连接,客户单的滑动窗口是4096,一次发送的最大数据量是1460
2.服务器接收连接情况,告诉客户端服务器的窗口大小是6144,一次发送的最大数据量是1024
3.第三次握手
4-9. 客户端连续给服务器发送了6k的数据,每次发送1k
10.,服务器告诉客户端:发送的6k数据以及接收到,存储在缓冲区中,缓冲区数据已经处理了2k,窗口大小是2k
11.服务器告诉客户端:发送的6k数据以及接收到,存储在缓冲区中,缓冲区数据已经处理了4k,窗口大小是4k
12.客户端给服务器发送了1k的数据
13.客户端主动请求和服务器断开连接并且给服务器发送1k数据
14.服务器回复ACK 8194 同意断开连接并且接收到了发的2k数据 滑动窗口大小2k
15-16. 通知客户端滑动窗口的大小
17. 第三次挥手 服务器端给客服端发送FIN 请求断开连接
18.第四次回收 客户端同意断开连接
TCP四次挥手
四次挥手发生在断开连接的时候,在程序中当调用了close()会使用TCP协议进行四次挥手客户端和服务器端都可以主动发起断开连接,
谁先调用close)谁就是发起。因为在TCP连接的时候,采用三次握手建立的的连接是双向的,在断开的时候需要双向断开
TCP通信并发
要实现TCP通信服务器处理并发的任务,使用多进程或线程来解决
多进程
思路:
1.一个父进程、多个子进程
2.父进程负责等待并接受客户端的连接
3.子进程完成通信 接受一个客户端连接就创建一个子进程通讯
服务器端
#define _POSIX_SOURCE
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<errno.h>>
void *recycleChild(void *arg){
while (1)
{
int ret=waitpid(-1,NULL,WNOHANG);
if(ret==-1){
//所有的子进程都被回收了
break;
}else if(ret==0){
//还有子进程没有结束
break;
}else if (ret>0)
{
//回收到了子进程
printf("子进程%d 被回收了",ret);
}
}
}
int main(){
//注册信号捕捉
struct sigaction act;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
act.sa_handler=recycleChild;
sigaction(SIGCHLD,&act,NULL);
//创建监听的套接字
int listenFd=socket(AF_INET,SOCK_STREAM,0);
if(listenFd==-1){
perror("socket");
exit(0);
}
//绑定
struct sockaddr_in saddr;
saddr.sin_family=AF_INET;
inet_pton(AF_INET,"172.19.2.247",&saddr.sin_addr.s_addr);
saddr.sin_port=htons(9999);
int ret=bind(listenFd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret==-1){
perror("bind");
exit(0);
}
//监听
ret=listen(listenFd,8);
if(ret==-1){
perror("listen");
exit(0);
}
int cfd;
//循环等待客户端连接
while (1)
{
//接受客户端连接
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
cfd=accept(listenFd,(struct sockaddr *)&clientaddr,&len);
if(cfd==-1){
//信号量回收子进程时会导致软中断cfd等于-1对于这个情况我们要单独处理不让父进程退出
if(errno==EINTR){
continue;
}
perror("accept");
exit(-1);
}
//每一个连接进来创建一个子进程通信
pid_t pid=fork();
if(pid==0){
//获取客户端的信息
char clientIP[16];
inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
unsigned short clientPOrt=ntohs(clientaddr.sin_port);
printf("ip %s port %d \n",clientIP,clientPOrt);
//获取客服端的信息
char recivebuf[1024]={0};
while (1)
{
ssize_t len1=read(cfd,recivebuf,sizeof(recivebuf));
if(len1==-1){
perror("read");
exit(-1);
}else if(len1>0){
printf("收到%s\n",recivebuf);
}else if(len1==0){
printf("客户端断开连接");
break;
}
write(cfd,recivebuf,strlen(recivebuf));
}
close(cfd);
exit(0);
}
//输出客户端的信息
}
close(listenFd);
return 0;
}
客户端
#include <arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
/*
// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
//创建套接字
int cfd=socket(AF_INET,SOCK_STREAM,0);
if (cfd==-1){
perror("socket");
exit(-1);
}
//连接服务器
struct sockaddr_in ser;
ser.sin_family=AF_INET;
const char src="172.19.2.247";
inet_pton(AF_INET,"172.19.2.247",&ser.sin_addr.s_addr);
ser.sin_port=htons(9999);
socklen_t len=sizeof(ser);
int ret=connect(cfd,(struct sockaddr *)&ser,len);
if (ret==-1){
perror("connet");
exit(-1);
}
char buf[1024]={0};
int i=0;
while (1)
{
//写数据
sprintf(buf,"这是第%d个\n",i);
int ret=write(cfd,buf,strlen(buf));
if (ret==-1){
perror("write");
exit(-1);
}
//读数据
ssize_t len1=read(cfd,buf,sizeof(buf));
if(len1==-1){
perror("read");
exit(-1);
}else if(len1>0){
printf("收到%s\n",buf);
}else if(len1==0){
printf("服务器丢失");
break;
}
sleep(1);
i++;
}
close(cfd);
return 0;
}
多线程
服务端
#define _POSIX_SOURCE
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<errno.h>
#include <pthread.h>
struct Message{
pthread_t tid;
int cfd;
struct sockaddr_in caddr;
};
struct Message Messages[10];
void *work(void * arg){
//子线程和客户端通信
struct Message *info=(struct Message*)arg;
char buf[1024]={0};
while (1)
{
int ret=read(info->cfd,buf,sizeof(buf));
if (ret==-1){
perror("read");
exit(-1);
}else if(ret>0){
printf("收到了 %s\n",buf);
}else{
printf("客户端断开连接 ");
break;
}
write(info->cfd,buf,strlen(buf));
}
close(info->cfd);
}
int main(){
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
//设置监听
ret=listen(lfd,10);
if (ret==-1){
perror("bind");
exit(-1);
}
//初始化数据
int max=sizeof(Messages)/sizeof(Messages[0]);
for(int i=0;i<max;i++){
bzero(&Messages[i],sizeof(Messages[i]));
Messages[i].cfd=-1;
Messages[i].tid=-1;
}
int cfd;
while (1)
{
//建立连接
struct sockaddr_in caddr;
socklen_t len=sizeof(caddr);
cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
if (ret==-1){
perror("bind");
exit(-1);
}
struct Message *info;
for (int i=0;i<max;i++){
if (Messages[i].cfd==-1){
info=&Messages[i];
break;
}
if (i==max-1){
sleep(1);
i=0;
}
}
info->cfd=cfd;
//创建线程
memcpy(&info->caddr,&caddr,len);
pthread_create(&info->tid,NULL,work,info);
pthread_detach(info->tid);
}
close(lfd);
}
客户端
#include <arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
/*
// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
//创建套接字
int cfd=socket(AF_INET,SOCK_STREAM,0);
if (cfd==-1){
perror("socket");
exit(-1);
}
//连接服务器
struct sockaddr_in ser;
ser.sin_family=AF_INET;
const char src="127.0.0.1";
inet_pton(AF_INET,"127.0.0.1",&ser.sin_addr.s_addr);
ser.sin_port=htons(9090);
socklen_t len=sizeof(ser);
int ret=connect(cfd,(struct sockaddr *)&ser,len);
if (ret==-1){
perror("connet");
exit(-1);
}
char buf[1024]={0};
int i=0;
while (1)
{
//写数据
sprintf(buf,"这是第%d个\n",i);
int ret=write(cfd,buf,strlen(buf));
if (ret==-1){
perror("write");
exit(-1);
}
//读数据
ssize_t len1=read(cfd,buf,sizeof(buf));
if(len1==-1){
perror("read");
exit(-1);
}else if(len1>0){
printf("收到%s\n",buf);
}else if(len1==0){
printf("服务器丢失");
break;
}
sleep(1);
i++;
}
close(cfd);
return 0;
}
TCP状态转换
2MSL(Maximum Segment Lifetime)
主动断开连接的一方, 最后进出入一个 TIME_WAIT状态, 这个状态会持续: 2msl
msl: 官方建议: 2分钟, 实际是30s
当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后,连接的主动关闭方
必须处于TIME_WAIT 状态并持续 2MSL 时间。
这样就能够让 TCP 连接的主动关闭方在它发送的 ACK 丢失的情况下重新发送最终的 ACK。
主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,
被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是
重传 FIN 直到它收到一个最终的 ACK。
半关闭
当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2
状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发
送的数据,但是 A 已经不能再向 B 发送数据。
半关闭
include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发
出写操作。
SHUT_RDWR(2):关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以
SHUT_WR。
使用 close 中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用。计数为 0 时才关闭连接。shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:
- 如果有多个进程共享一个套接字,close 每被调用一次,计数减 1 ,直到计数为 0 时,也就是所用进程都调用了 close,套接字将被释放。
- 在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后,其它的进程将无法进行通信。但如果一个进程 close(sfd) 将不会影响到其它进程。
端口复用
端口复用最常用的用途是:
防止服务器重启时之前绑定的端口还未释放
程序突然退出而系统没有释放端口
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t
optlen)
sockfd 需要复用的套接字的文件描述符
level 级别 SOL_SOCKET 端口复用的级别
optname 选项的名称 SO_REUSEADDR SO_REUSEPORT
optval 端口复用的值 1可以复用 0不可以复用
optlen optval的大小
端口复用,设置的时机是在服务器绑定端口之前
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
I/O多路复用
I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的
系统调用主要有 select、poll 和 epoll。
Select
主旨思想:
- 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
- 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O
操作时,该函数才返回。
a.这个函数是阻塞
b.函数对文件描述符的检测的操作是由内核完成的 - 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作
/*
/ sizeof(fd_set) = 128 1024
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds : 委托内核检测的最大文件描述符的值 + 1
readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
一般检测读操作 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区
writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)
exceptfds : 检测发生异常的文件描述符的集合
timeout : 设置的超时时间
struct timeval {
long tv_sec;//seconds
long tv_usec; //microseconds
};
- NULL : 永久阻塞,直到检测到了文件描述符有变化
- tv_sec = 0 tv_usec = 0, 不阻塞
- tv_sec > 0 tv_usec > 0, 阻塞对应的时间
返回值 : -1 : 失败 >0(n) : 检测的集合中有n个文件描述符发生了变化
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);
// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);
// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);
*/
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
int main(){
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
int optval=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
//设置监听
ret=listen(lfd,10);
if (ret==-1){
perror("bind");
exit(-1);
}
//创建一个fd_set 保存需要检测的文件描述符集合
fd_set rdset,tmp;
FD_ZERO(&rdset);
FD_SET(lfd,&rdset);
int maxfd=lfd;
while (1)
{
tmp=rdset;
//select 让内核检测文件描述符的数据
int ret=select(maxfd+1,&tmp,NULL,NULL,NULL);
if (ret==-1){
perror("select");
exit(-1);
}else if(ret==0){
continue;
}else if(ret>0){
//检测到了新的连接
if (FD_ISSET(lfd,&tmp)){
struct sockaddr_in caddr;
socklen_t len=sizeof(caddr);
int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
if (ret==-1){
perror("bind");
exit(-1);
}
//将新的文件描述符加入到集合中
FD_SET(cfd,&rdset);
maxfd=maxfd>cfd?maxfd:cfd;
}
//已经连接的有写数据的
for(int i=lfd+1;i<=maxfd;i++){
if(FD_ISSET(i,&tmp)){
//读写操作
char buf[1024]={0};
int ret=read(i,buf,sizeof(buf));
printf("%d\n",ret);
if (ret==-1){
perror("read");
exit(-1);
}else if(ret>0){
printf("收到了 %s\n",buf);
write(i,buf,strlen(buf));
}else {
printf("客户端断开连接");
close(i);
FD_CLR(i,&rdset);
}
}
}
}
}
close(lfd);
}
Poll
/*
#include <poll.h>
struct pollfd {
int fd; //委托内核检测的文件描述符
short events; 委托内核检测文件描述符的什么事件
short revents; 文件描述符实际发生的事件
};
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
timeout : 阻塞时长 0 : 不阻塞 -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞 >0 : 阻塞的时长(单位是毫秒)
返回值:-1 : 失败 >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化*/
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(){
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
int optval=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
//设置监听
ret=listen(lfd,10);
if (ret==-1){
perror("bind");
exit(-1);
}
//初始化检测的文件描述符数组
struct pollfd fds[1024];
for(int i=0;i<1024;i++){
fds[i].fd=-1;
fds[i].events=POLLIN;
}
fds[0].fd=lfd;
int nfds=0;
while (1)
{
int ret=poll(fds,nfds+1,-1);
if (ret==-1){
perror("poll");
exit(-1);
}else if(ret==0){
continue;
}else if(ret>0){
//检测到了新的连接
if (fds[0].revents&POLLIN){
struct sockaddr_in caddr;
socklen_t len=sizeof(caddr);
int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
if (ret==-1){
perror("bind");
exit(-1);
}
//将新的文件描述符加入到集合中
for(int i=1;i<1024;i++){
if(fds[i].fd==-1){
fds[i].fd=cfd;
fds[i].events=POLLIN;
break;
}
if(i==1023){
sleep(1);
i=0;
}
}
//更新最大的文件索引
nfds=nfds>cfd?nfds:cfd;
}
//已经连接的有写数据的
for(int i=1;i<=nfds;i++){
if(fds[i].revents&POLLIN){
//读写操作
char buf[1024]={0};
int ret=read(fds[i].fd,buf,sizeof(buf));
if (ret==-1){
perror("read");
exit(-1);
}else if(ret>0){
printf("收到了 %s\n",buf);
write(fds[i].fd,buf,strlen(buf));
}else {
printf("客户端断开连接");
close(fds[i].fd);
fds[i].fd=-1;
}
}
}
}
}
close(lfd);
}
Epoll
/*
#include <sys/epoll.h>
// 创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,
一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。
int epoll_create(int size);
size : 目前没有意义了。随便写一个数,必须大于0
返回值:-1 : 失败 > 0 : 文件描述符,操作epoll实例的
Epoll 的工作模式:
LT 模式 (水平触发)
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知
LT(level - triggered)
是缺省的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操
作。如果你不作任何操作,内核还是会继续通知你的。
ET 模式(边沿触发)
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知
ET(edge - triggered)是高速工作方式,只支持 no-block socket。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,
并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。
但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,
必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
epfd : epoll实例对应的文件描述符
op : 要进行什么操作 :EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除
fd : 要检测的文件描述符
event : 检测文件描述符什么事情
struct epoll_event {
uint32_t events; Epoll events
epoll_data_t data; User data variable
};
常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);// 检测函数
epfd : epoll实例对应的文件描述符
events : 传出参数,保存了发送了变化的文件描述符的信息
maxevents : 第二个参数结构体数组的大小
timeout : 阻塞时间 0 : 不阻塞 -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 > 0 : 阻塞的时长(毫秒)
返回值:成功,返回发送变化的文件描述符的个数 > 0 失败 -1
struct epoll_event {
uint32_t events; Epoll events
epoll_data_t data; User data variable
};
常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET
*/
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include<fcntl.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
#include<errno.h>
#include<error.h>
int main(){
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
int optval=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
//设置监听
ret=listen(lfd,10);
if (ret==-1){
perror("bind");
exit(-1);
}
//创建一个epoll实例
int epfd=epoll_create(100);
//将监听的文件描述符加入到epoll实例当中
struct epoll_event epev;
epev.events=EPOLLIN;
epev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
struct epoll_event epevs[5];
while (1)
{
int ret=epoll_wait(epfd,epevs,5,-1);
if (ret==-1){
perror("epoll_wait");
exit(-1);
}
for(int i=0;i<ret;i++){
if (epevs[i].data.fd==lfd){
//有客户端连接
struct sockaddr_in caddr;
socklen_t len=sizeof(caddr);
int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
//设置cfdf非阻塞
int flag=fcntl(cfd,F_GETFL);
flag|O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
if (ret==-1){
perror("bind");
exit(-1);
}
epev.events=EPOLLIN|EPOLLET;//设置边缘触发
epev.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
}else{
//循环读取所有数据
int len=0;
char buf[5];
while ((len=read(epevs[i].data.fd,buf,sizeof(buf)))>0)
{
printf("收到了 %s\n",buf);
write(epevs[i].data.fd,buf,strlen(buf));
}
if(len==0){
printf("客户端断开连接");
epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
close(epevs[i].data.fd);
}
else if(len==-1){
if (errno==EAGAIN){
printf("数据结束");
}else{
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
}
UDP
udp服务端
/*
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);发送数据
sockfd : 通信的fd
buf : 要发送的数据
len : 发送数据的长度
flags : 0 一般不会用
dest_addr : 通信的另外一端的地址信息
addrlen : 地址的内存大小
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);接受数据
sockfd : 通信的fd
buf : 接收数据的数组
len : 数组的大小
flags : 0
src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
addrlen : 地址的内存大小
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
char buf[1024];
char ipbuf[16];
//通信
while (1)
{
struct sockaddr_in caddr;
int len=sizeof(caddr);
int num=recvfrom(lfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&len);
printf("client iP %s port %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(caddr.sin_port));
printf("client say %s\n",buf);
sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&caddr,sizeof(caddr));
}
close(lfd);
}
udp客户端
/*
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);发送数据
sockfd : 通信的fd
buf : 要发送的数据
len : 发送数据的长度
flags : 0 一般不会用
dest_addr : 通信的另外一端的地址信息
addrlen : 地址的内存大小
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);接受数据
sockfd : 通信的fd
buf : 接收数据的数组
len : 数组的大小
flags : 0
src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
addrlen : 地址的内存大小
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
char buf[1024];
char ipbuf[16];
//通信
while (1)
{
struct sockaddr_in caddr;
int len=sizeof(caddr);
int num=recvfrom(lfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&len);
printf("client iP %s port %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(caddr.sin_port));
printf("client say %s\n",buf);
sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&caddr,sizeof(caddr));
}
close(lfd);
}
广播
客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"172.19.15.255",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
char getbuf[1024];
//通信
while (1)
{
printf("1\n");
int num=recvfrom(lfd,getbuf,sizeof(getbuf),0,NULL,NULL);
printf("收到了 %s\n",getbuf);
}
close(lfd);
return 0;
}
服务端
/*
/ 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t
optlen);
- sockfd : 文件描述符
- level : SOL_SOCKET
- optname : SO_BROADCAST
- optval : int类型的值,为1表示允许广播
- optlen : optval的大小
*/
/*
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);发送数据
sockfd : 通信的fd
buf : 要发送的数据
len : 发送数据的长度
flags : 0 一般不会用
dest_addr : 通信的另外一端的地址信息
addrlen : 地址的内存大小
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);接受数据
sockfd : 通信的fd
buf : 接收数据的数组
len : 数组的大小
flags : 0
src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
addrlen : 地址的内存大小
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;//广播的IP
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"172.19.15.255",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
//设置广播属性
int op=1;
ret=setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));
if (ret==-1){
perror("inet_pton");
exit(-1);
}
//通信
char buf[1024];
int num=0;
while (1)
{
sprintf(buf,"hello %d \n",num++);
sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&addr,sizeof(addr));
printf("广播 %s",buf);
sleep(1);
}
close(lfd);
return 0;
}
组播(多播)
单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。
单播和广播是寻址方案的两个极端(要么单个要么全部)
多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。
a.组播既可以用于局域网,也可以用于广域网
b.客户端需要加入多播组,才能接收到多播的数据
/*
int setsockopt(int sockfd, int level, int optname,const void *optval,socklen_t optlen);//
服务器设置多播的信息,外出接口
- level : IPPROTO_IP
- optname : IP_MULTICAST_IF
- optval : struct in_addr
客户端加入到多播组:
- level : IPPROTO_IP
- optname : IP_ADD_MEMBERSHIP
- optval : struct ip_mreq
struct ip_mreq
{
IP multicast address of group.
struct in_addr imr_multiaddr; // 组播的IP地址
Local IP address of interface.
struct in_addr imr_interface; // 本地的IP地址
};
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};*/
服务端
/*
int setsockopt(int sockfd, int level, int optname,const void *optval,socklen_t optlen);//
服务器设置多播的信息,外出接口
- level : IPPROTO_IP
- optname : IP_MULTICAST_IF
- optval : struct in_addr
客户端加入到多播组:
- level : IPPROTO_IP
- optname : IP_ADD_MEMBERSHIP
- optval : struct ip_mreq
struct ip_mreq
{
IP multicast address of group.
struct in_addr imr_multiaddr; // 组播的IP地址
Local IP address of interface.
struct in_addr imr_interface; // 本地的IP地址
};
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//设置多播得地址
struct in_addr multiaddr;
inet_pton(AF_INET,"239.0.0.10",&multiaddr.s_addr);
//设置多播属性
setsockopt(lfd,IPPROTO_IP,IP_MULTICAST_IF,&multiaddr,sizeof(multiaddr));
//初始化客户端得多播地址
struct sockaddr_in addr;//广播的IP
addr.sin_family=AF_INET;
int ret=inet_pton(AF_INET,"239.0.0.10",&addr.sin_addr);
if (ret==-1){
perror("inet_pton");
exit(-1);
}
addr.sin_port=htons(9090);
//通信
char buf[1024];
int num=0;
while (1)
{
sprintf(buf,"hello %d \n",num++);
sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&addr,sizeof(addr));
printf("组播 %s",buf);
sleep(1);
}
close(lfd);
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
int lfd=socket(AF_INET,SOCK_DGRAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定ip和端口
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=INADDR_ANY;
addr.sin_port=htons(9090);
int ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
//加入多播组
struct ip_mreq op;
inet_pton(AF_INET,"239.0.0.10",&op.imr_multiaddr.s_addr);
op.imr_interface.s_addr=INADDR_ANY;
//设置多播属性
setsockopt(lfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&op,sizeof(op));
char getbuf[1024];
//通信
while (1)
{
printf("1\n");
int num=recvfrom(lfd,getbuf,sizeof(getbuf),0,NULL,NULL);
printf("收到了 %s\n",getbuf);
}
close(lfd);
return 0;
}
本地套接字
服务端
/*
// 本地套接字通信的流程 - tcp
// 服务器端
1. 创建监听的套接字
int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 -> server端
struct sockaddr_un addr;
// 绑定成功之后,指定的sockaddr_un.sun_path中的套接字文件会自动生成。
bind(lfd, addr, len);
3. 监听
listen(lfd, 100);
4. 等待并接受连接请求
struct sockaddr_un cliaddr;
int cfd = accept(lfd, &cliaddr, len);
5. 通信
接收数据:read/recv
发送数据:write/send
6. 关闭连接
close();
// 客户端的流程
1. 创建通信的套接字
int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口
struct sockaddr_un addr;
// 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
bind(lfd, addr, len);
3. 连接服务器
struct sockaddr_un serveraddr;
connect(fd, &serveraddr, sizeof(serveraddr));
4. 通信
接收数据:read/recv
发送数据:write/send
5. 关闭连接
close();
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
#include<sys/un.h>
int main(){
unlink("server.sock");//每次运行前删掉对应的文件
int lfd=socket(AF_LOCAL,SOCK_STREAM,0);
if (lfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定本地
struct sockaddr_un addr;
addr.sun_family=AF_LOCAL;
strcpy(addr.sun_path,"server.sock");
bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
//监听
listen(lfd,5);
//建立连接
struct sockaddr_un cliaddr;
int len=sizeof(cliaddr);
int cfd=accept(lfd,(struct sockaddr *)&cliaddr,&len);
if (cfd<0){
perror("accept");
exit(1);
}
printf("客户端的文件 %s\n",cliaddr.sun_path);
//通信
char buf[1024];
int num=0;
while (1)
{ int len=recv(cfd,buf,sizeof(buf),0);
if (len==-1){
perror("recv");
exit(-1);
}else if(len==0){
printf("客户端断开连接\n");
break;
}else if(len>0){
printf("收到了 %s",buf);
send(cfd,buf,len,0);
}
sleep(1);
}
close(cfd);
close(lfd);
return 0;
}
客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include<sys/un.h>
#include <poll.h>
int main(){
unlink("client.sock");//每次运行前删掉对应的文件
int cfd=socket(AF_LOCAL,SOCK_STREAM,0);
if (cfd==-1){
perror("socket");
exit(-1);
}
//为套接字绑定本地
struct sockaddr_un addr;
addr.sun_family=AF_LOCAL;
strcpy(addr.sun_path,"client.sock");
int ret=bind(cfd,(struct sockaddr *)&addr,sizeof(addr));
if (ret==-1){
perror("bind");
exit(-1);
}
//建立连接
struct sockaddr_un Serveaddr;
Serveaddr.sun_family=AF_LOCAL;
strcpy(Serveaddr.sun_path,"server.sock");
socklen_t len=sizeof(Serveaddr);
ret=connect(cfd,(struct sockaddr *)&Serveaddr,len);
if (ret==-1){
perror("connect");
exit(-1);
}
//通信
char buf[1024];
int num=0;
while (1)
{
sprintf(buf,"数字是%d\n",num);
send(cfd,buf,strlen(buf)+1,0);
printf("客户端说%s\n",buf);
int len=recv(cfd,buf,sizeof(buf),0);
if (len==-1){
perror("recv");
exit(-1);
}else if(len==0){
printf("客户端断开连接\n");
close(cfd);
break;
}else if(len>0){
printf("收到了 %s",buf);
}
sleep(1);
num++;
}
close(cfd);
return 0;
}