socket编程常用函数笔记

网友投稿 788 2022-11-28

socket编程常用函数笔记

socket编程常用函数笔记

文章目录

​​一、什么是Socket​​​​二、套接字地址结构​​​​三、套接字函数使用​​​​四、套接字类型​​​​五、socket编程​​

​​1、服务器端函数​​​​2、客户端函数​​​​3、其他函数​​

​​六、完整的tcp server代码​​​​七、完整的tcp client代码​​​​八、完整的多进程tcp client代码​​

​​流式服务和粘包问题​​​​TCP状态转移总图​​

​​九、完整的udp server代码​​​​十、完整的udp client代码​​​​十一、对比TCP字节流服务和UDP数据包服务​​​​十二、一个单线程的HTTP服务器​​​​十三、libevent的使用​​

一、什么是Socket

Socket可以看成是用户进程与内核网络协议栈的编程接口Socket不仅可以用于本机的进程通信,还可以用于网络上不同主机的进程间通信

二、套接字地址结构

IPv4地址结构,以 “sockaddr_in”命名,定义在头文件

typedef uint32_t in_addr_t;struct in_addr { in_addr_t s_addr; };struct sockaddr_in { uint8_t sin_len;//无符号8位整数,1B sa_family_t sin_family;//1B in_port_t sin_port; // 端口号,无符号16位整数,0~65535,2B struct in_addr sin_addr;//网络地址,4B char sin_zero[8];//8B};

sin_len:整个sockaddr_in结构体的长度sin_family:指定地址家族,IPv4中是AF_INETsin_port:端口sin_addr:IPv4的地址sin_zero:一般设为0

填信息的时候一律用​​sockaddr_in ​​​,传入API的时候用​​sockaddr​​

通用地址结构

struct sockaddr{ uint8_t sa_len;//无符号8位整数,1B sa_family_t sa_family;//1B char sa_data[14];//14B};

头文件:sin_len:整个sockaddr_in结构体的长度sin_family:指定地址家族,IPv4中是AF_INET,IPv6中是AF_INET6sa_data:由sin_family决定大小

三、套接字函数使用

字节序转换函数

h表示hostn表示networkl表示longs表示short

uint32_t htonl(uint32_t host);//4个字节整数,由主机字节序转换为网络字节序,网络字节序为大端字节序uint16_t htons(uint16_t host);//2个字节整数,由主机字节序转换为网络字节序uint32_t ntohl(uint32_t net);//4个字节整数,由网络字节序准换为主机字节序uint16_t ntohs(uint16_t net);//2个字节整数,由网络字节序准换为主机字节序

#include#include/** * 验证X86平台为小端字节序 * 网络字节序为大端字节序 */int main(void){ unsigned int x = 0x12345678; unsigned char* p_host = (unsigned char*)&x; printf("%0x %0x %0x %0x\n", p_host[0],p_host[1],p_host[2],p_host[3]);//78 56 34 12,小端字节序 unsigned int y = htonl(x); unsigned char *p_net = (unsigned char *)&y; printf("%0x %0x %0x %0x\n", p_net[0],p_net[1],p_net[2],p_net[3]);//12 34 56 78 return 0;}

地址转换函数

int inet_aton(const char* cp, struct in_addr* inp);//点分十进制的ip地址转换成网络字节序的ip地址in_addr_t inet_addr(const char* cp);//点分十进制的ip地址转换成32位整数char* inet_ntoa(struct in_addr in);//32位整数转换成点分十进制的ip地址

#include#includeint main(void){ unsigned long addr = inet_addr("192.168.0.100"); //先将网络字节序转换为主机字节序,再输出 printf("addr=%u\n", ntohl(addr));//addr=3232235620 struct in_addr ipAddr; ipAddr.s_addr = addr; printf("%s\n", inet_ntoa(ipAddr));//192.168.0.100 return 0;}

四、套接字类型

流式套接字(SOCK_STREAM)

提供面向连接的、可靠的数据传输服务,数据无差错,无重复发送,且按序接收(TCP)

数据报式套接字(SOCK_DGRAM)

提供无连接服务,数据可能丢失或重复,可能顺序混乱(UDP)

原始套接字(SOCK_RAW)

五、socket编程

1、服务器端函数

创建套接字

原型:int socket(int domain, int type, int protocol)domain:指定通信协议族,IPv4(AF_INET),IPv6(AF_INET6)type:指定socket类型,流式套接字(SOCK_STREAM)、数据报式套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)protocol:协议类型,一般写成0返回值:一般称之为套接字描述字(类似于文件描述符),成功返回非负整数,失败返回-1

int sock = socket(AF_INET, SOCK_STREAM, 0);//套接口描述字if ( sock < 0) { printf("socket error"); return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(7777);//需要写网络字节的端口号server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//若成功,返回32位二进制的网络字节序地址;若有错,则返回 INADDR-NONE //inet_aton("127.0.0.1", &server_addr.sin_addr); //点分十进制改成二进制

绑定函数(数据从进程到内核,服务器)

功能:绑定一个本地地址到套接字原型:int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen)*sockfd:socket函数返回的套接字addr:要绑定的套接字addrlen:套接字地址长度,IPv4为16,IPv6为24返回值:成功返回0,失败返回-1

int bindfd = bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr));//服务器端绑定端口if (bindfd < 0) { printf("bind error"); return -1;}

监听函数(服务器)

功能:将套接字用于监听进入的连接原型:int listen(int sockfd, int backlog)sockfd:socket函数返回的套接字backlog:规定内核为此套接字排队的最大连接数返回值:成功返回0,失败返回-1

listen只是把套接字从主动变为被动,并限制链接数;剩下的问题就是accept的,它会检测的; listen意思是监听:但是它不是一直在监听,accept才是;

listen函数不会阻塞,它只是相当于把socket的属性更改为被动连接,可以接收其他进程的连接。listen侦听的过程并不是一直阻塞,直到有客户端请求连接才会返回,它只是设置好socket的属性之后就会返回。监听的过程实质由操作系统完成。但是accept会阻塞(也可以设置为非阻塞),如果listen的套接字对应的连接请求队列为空(没有客户端连接请求),accept会一直阻塞等待

if (listen(listenfd, SOMAXCONN) < 0) { printf("listen error"); return -1;}

连接函数(服务器)

功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞原型:int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)sockfd:服务器套接字addr:将返回对方的套接字地址addrlen:返回对方的套接字地址长度返回值:成功返回非负描述字conn,失败返回-1

struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int conn = accept(listenfd, (struct sockaddr*) & client_addr, &client_len);//主动套接字,内核到用户进程,拿数据(拿客户端的相关连接信息)if (conn < 0) { printf("accept error"); return -1;}

2、客户端函数

connect函数

int connect(int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)

返回值

0:成功-1:失败

3、其他函数

地址转换函数

int inet_aton(const char* cp, struct in_addr* inp);//点分十进制的ip地址转换成网络字节序的ip地址in_addr_t inet_addr(const char* cp);//点分十进制的ip地址转换成32位整数char* inet_ntoa(struct in_addr in);//32位整数转换成点分十进制的ip地址

server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");inet_aton("127.0.0.1", &server_addr.sin_addr);

新型网路地址转化函数inet_pton和inet_ntop(to point)

头文件:#include int inet_pton(int family, const char *strptr, void *addrptr) //将点分十进制的ip地址转化为用于网络传输的数值格式返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式返回值:若成功则为指向结构的指针,若出错则为NULL

int main (void){ char IPdotdec[20]; //存放点分十进制IP地址 struct in_addr s; // IPv4地址结构体 // 输入IP地址 printf("Please input IP address: "); scanf("%s", IPdotdec); // 转换 inet_pton(AF_INET, IPdotdec, (void *)&s);//点分十进制的ip地址转换为二进制放到s printf("inet_pton: 0x%x\n", s.s_addr); // 注意得到的字节序 // 反转换 inet_ntop(AF_INET, (void *)&s, IPdotdec, 16); printf("inet_ntop: %s\n", IPdotdec);}

inet_pton(AF_INET, IPdotdec, &src.sin_addr);//src.sin_addr.s_addr = inet_addr(IPdotdec);char IPdotdec[INET_ADDRATRLEN];ptr = inet_ntop(AF_INET, &src.sin_addr, IPdotdec, sizeof(IPdotdec));//ptr = inet_ntoa(src.sin_addr);

readn函数

*ssize_t readn(int fd, void vptr, size_t n)功能:从描述字中读取n个字节为止或读到EOF为止返回值:

1.大于0,代表成功读取的字节数2.等于0,代表读取到了EOF,一般是对方关闭了socket的写端或者直接close3.小于0,出现错误。

ssize_t readn(int fd, void *vptr, size_t n){ size_t nleft;//剩余读取字节数 ssize_t nread;//已经读取的字节数 char *ptr; ptr = vptr; nleft = n;//总共需要读取的字节数 while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; /* and call read() again */ else return(-1); } else if (nread == 0) break; /* 读到了EOF */ //正确从描述字读取到nread字节的数据 nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */}ssize_t Readn(int fd, void *ptr, size_t nbytes){ ssize_t n; if ( (n = readn(fd, ptr, nbytes)) < 0) err_sys("readn error"); return(n);}

writen函数

原型:*ssize_t writen(int fd, const void vptr, size_t n)功能:往描述字中写入n个字节返回值

小于0:错误大于0:成功写入n个字节

/* Write "n" bytes to a descriptor. */ssize_t writen(int fd, const void *vptr, size_t n){ size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (errno == EINTR) nwritten = 0; /* and call write() again */ else return(-1); /* error */ } nleft -= nwritten; ptr += nwritten; } return(n);}/* end writen */void Writen(int fd, void *ptr, size_t nbytes){ if (writen(fd, ptr, nbytes) != nbytes) err_sys("writen error");}

readline函数

原型:ssize_t readline(int fd, void *vptr, size_t maxlen)功能:从描述字中一个字节一个字节地读取放到缓冲区vptr,直到读到最大长度maxlen,或读到换行符,或读完数据返回值

大于0:返回的是成功读取到的字节数等于0:没有数据读取小于0:错误

ssize_t readline(int fd, void *vptr, size_t maxlen){ ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) {again: if ( (rc = read(fd, &c, 1)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { if (n == 1) return(0); /* EOF, no data read */ else break; /* EOF, some data was read */ } else { if (errno == EINTR) goto again; return(-1); /* error, errno set by read() */ } } *ptr = 0; /* null terminate like fgets() */ return(n);}/* end readline */ssize_t Readline(int fd, void *ptr, size_t maxlen){ ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < 0) err_sys("readline error"); return(n);}

read函数

原型:ssize_t write(int fd, const void*buf,size_t nbytes);功能:将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量返回值

大于0,表示写了部分或者是全部的数据小于0,此时出现了错误

write函数

原型:*ssize_t read(int fd,void buf,size_t nbyte)功能:read函数是负责从fd中读取内容返回值

大于0,返回的是实际所读的字节数小于0,表示出现了错误

socket套接字是全双工的

六、完整的tcp server代码

#include#include#include#include#include#include#includeint main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ printf("create socket failed!\n"); return 0; } // 两个套接字地址 struct sockaddr_in ser_addr, cli_addr; memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; // 地址族 ser_addr.sin_port = htons(8888); // host to net short ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // inet_addr:字符串转成整型的网络字节序点分十进制的ip地址 // sockfd就是参观门口的服务员 ser_addr就是餐厅的地址 bind表示sockfd服务员为sockaddr餐厅工作 // 给监听套接字指定ip port // sockfd表示手机 ser_addr是手机卡 ser_addr是卡大小 bind表示给手机插卡 int res = bind(sockfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); // struct sockaddr* 通用套接字地址结构 if(res == -1){ printf("bind failed!\n"); return 0; } // 套接字 监听队列的大小 listen(sockfd, 5); // 创建监听队列(已完成三次握手的连接长度) while(1){ int len = sizeof(cli_addr); // accept从监听队列中取出连接,将信息取出来存入cli_addr。conn是文件描述符,就是餐馆内点菜的服务员 int conn = accept(sockfd, (struct sockaddr*)&cli_addr, &len); if(conn < 0){ // 取出连接失败 continue; } printf("accept conn = %d\n", conn); while(1){ char buff[128] = {0}; // ssize_t recv(int sockfd, void *buf, size_t len, int flags); int n = recv(conn, buff, 127, 0); // client断开连接,就会返回0 if(n == 0){ printf("client %d exit\n", conn); break; } printf("buff(%d) = %s\n", n, buff); // ssize_t send(int sockfd, const void *buf, size_t len, int flags); send(conn, "welcome connect server!\n", sizeof("welcome connect server!\n"), 0); } close(conn); } return 0;}

七、完整的tcp client代码

#include#include#include#include#include#include#include#includeint main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ printf("create socket failed!\n"); return 0; } struct sockaddr_in cli_addr; memset(&cli_addr, 0, sizeof(cli_addr)); cli_addr.sin_family = AF_INET; // 地址族 cli_addr.sin_port = htons(8888); // host to net short cli_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // inet_addr将字符串转为无符号整型 // 可以将套接字绑定ip,但一般客户端不绑定,让OS随机分配port int res = connect(sockfd, (struct sockaddr*)&cli_addr, sizeof(cli_addr)); // 连接server assert(res != -1); while(1){ char buff[128] = {0}; printf("input:"); fgets(buff, 128, stdin); if(strcmp(buff, "exit\n") == 0){ break; } send(sockfd, buff, strlen(buff), 0); memset(buff, 0 ,128); int n = recv(sockfd, buff, 127, 0); printf("buff(%d) = %s", n, buff); } close(sockfd); return 0;}

八、完整的多进程tcp client代码

#include #include #include #include #include #include #include #include #include // 开启一个子进程服务客户端cli_addrvoid serve_client(int cli_fd, struct sockaddr* cli_addr){ pid_t pid = fork(); if(pid < 0){ printf("fork error!\n"); close(cli_fd); return ; }else if(pid == 0){ while(1){ char buff[128] = {0}; // ssize_t recv(int listen_sock, void *buf, size_t len, int flags); int n = recv(cli_fd, buff, 127, 0); // client断开连接,就会返回0 if(n == 0){ printf("client %d exit\n", cli_fd); break; } printf("from client %s:%d:\n", inet_ntoa(((struct sockaddr_in*)cli_addr)->sin_addr), ntohs(((struct sockaddr_in*)cli_addr)->sin_port)); printf("buff(%d) = %s\n", n, buff); // ssize_t send(int listen_sock, const void *buf, size_t len, int flags); send(cli_fd, "ok\n", sizeof("ok\n"), 0); } close(cli_fd); // 子进程使用完close exit(0); }else{ // 父进程 close(cli_fd); // fork后,父子进程都使用同一个cli_fd,cli_fd会有引用计数,父进程不用先close waitpid(pid,NULL,WNOHANG|WUNTRACED); // 告诉kernel,提醒处理zombies }}int main(){ int listen_sock = socket(AF_INET, SOCK_STREAM, 0); if(listen_sock == -1){ printf("create socket failed!\n"); return 0; } // 两个套接字地址 struct sockaddr_in ser_addr, cli_addr; memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; // 地址族 ser_addr.sin_port = htons(8888); // host to net short ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // inet_addr将字符串转为无符号整型 // listen_sock就是参观门口的服务员 ser_addr就是餐厅的地址 bind表示listen_sock服务员为sockaddr餐厅工作 // 给监听套接字指定ip port // listen_sock表示手机 ser_addr是手机卡 ser_addr是卡大小 bind表示给手机插卡 int res = bind(listen_sock, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); // struct sockaddr* 通用套接字地址结构 if(res == -1){ printf("bind failed!\n"); return 0; } // 套接字 监听队列的大小 listen(listen_sock, 1); // 创建监听队列(已完成三次握手的连接长度) while(1){ int len = sizeof(cli_addr); // accept从监听队列中取出连接,将信息取出来存入cli_addr。client_fd是文件描述符,就是餐馆内点菜的服务员 int client_fd = accept(listen_sock, (struct sockaddr*)&cli_addr, &len); if(client_fd < 0){ // 取出连接失败 printf("客户端连接失败\n"); continue; } printf("accept client_fd = %d connected\n", client_fd); // 在这里服务客户,父进程和子进程都会关闭描述符 serve_client(client_fd, (struct sockaddr*)&cli_addr); } return 0;}

抓包命令: ​​tcpdump -i <网卡> -nt -S '(src dst) or (src dst)'​​

流式服务和粘包问题

HTTP协议解决粘包问题: 在每个数据报头写入当前报文的长度,接收端会根据该长度控制或分割自己接收的数据包。

TCP状态转移总图

TIME_WAIT状态的意义: 主动关闭的一方 收到对方的FIN,自己回复ACK后,并没有直接进入CLOSED状态,而是进入TIME_WAIT状态一段时间(大约是报文最长生存时间的2倍)。

若先关闭的一方回复ACK后,直接进入CLOSED状态,假如ACK丢失了,后关闭的一方会重发FIN,此时先关闭的一方无法接收,四次挥手无法完成。(若后关闭的一方不重发FIN,则说明成功收到ACK)进入TIME_WAIT,若后关闭的一方没收到ACK,会再次发送FIN,进入TIME_WAIT的一方重发ACK。

为什么TIME_WAIT状态大约是报文最长生存时间的2倍? 假设网络拥塞,数据包在网络中传送了很长时间,若发送ACK后没有TIME_WAIT状态直接关闭连接,而这时又收到了迟到的数据包就无法处理。 TIME_WAIT状态大约是报文最长生存时间的2倍,是为了保证能正确收到所有能被送达的数据包。 若先关闭的一方处于TIME_WAIT状态,则程序无法重新启动

九、完整的udp server代码

udp编程流程

#include#include#include#include#include#include#include#includeint main(){ int sockfd = socket(AF_INET, SOCK_DGRAM, 0); assert(sockfd != -1); struct sockaddr_in ser_addr; memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; ser_addr.sin_port = htons(8080); ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 指定socket的ip和端口,ip指定主机,端口指定进程 int res = bind(sockfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); assert(res != -1); while(1){ // 用于存储客户端的地址 struct sockaddr_in cli_addr; int len = sizeof(cli_addr); char buff[128] = {0}; // 把收到的数据放在buff,ip放在cli_addr int n = recvfrom(sockfd, buff, 127, 0, (struct sockaddr*)&cli_addr, &len); printf("recv(%d) = %s", n, buff); sendto(sockfd, "welcome connect server!\n", sizeof("welcome connect server!\n"),0, (struct sockaddr*)&cli_addr,sizeof(cli_addr)); } return 0;}

十、完整的udp client代码

#include#include#include#include#include#include#include#includeint main(){ int sockfd = socket(AF_INET, SOCK_DGRAM, 0); assert(sockfd != -1); struct sockaddr_in ser_addr; memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; ser_addr.sin_port = htons(8080); ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); while(1){ char buff[128] = {0}; printf("input:"); fflush(stdout); fgets(buff, 128, stdin); if(strcmp(buff, "exit\n") == 0){ break; } // 通过sockfd发送buff给ser_addr // 服务器需要bind,而客户端不需要bind,是因为客户端的端口默认由系统自行分配 sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); int len = sizeof(ser_addr); int n = recvfrom(sockfd, buff, 127, 0, (struct sockaddr*)&ser_addr, &len); printf("recv(%d) = %s", n, buff); } close(sockfd); return 0;}

十一、对比TCP字节流服务和UDP数据包服务

TCP服务中将收取的字节改为1

int n = recv(conn, buff, 1, 0);

UDP服务中将收取的字节改为1

recvfrom(sockfd, buff, 1, 0, (struct sockaddr*)&cli_addr, &len);

tcp可以把多次send的数据放到缓冲区然后发送,是因为tcp发送的数据一定是发送给指定的主机。而udp不能合并报文,是因为每个udp数据报去往的主机不一定相同。

对于tcp而言,客户端发送一个报文​​hello​​​到了tcp缓冲区,虽然进程一次​​recv​​只能取一个字节,但只要tcp缓冲区不空,就会不断执行while循环,不断 从缓冲区中取数据。 对于udp而言,一次​​​recvfrom​​直接从数据报中取数据 ,只能取一个字节,由于udp服务没有数据缓冲区,所以一个报文的数据没收完就丢失了。

面试题:如果server和client建立连接后,server的网线断了,然后断电重启联网,重启进程后,server和client分别处于什么状态?

断网断电后,server无法发出任何的报文通知client,所以client认为连接就是正常的,除非client发出报文,才会发现连接异常。服务器重启后,和原来客户端的连接已经丢失,处于监听状态

十二、一个单线程的HTTP服务器

#include#include#include#include#include#include#include#include#includechar* get_filename(char msg[]){ // 例:msg = "GET /index.html HTTP/1.1" if(msg == NULL){ return NULL; } char* str = strtok(msg, " "); if(str == NULL){ return NULL; } printf("请求的方法是:%s\n", str); str = strtok(NULL, " "); if(str == NULL){ return NULL; } return str;}int socket_init(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ return -1; } struct sockaddr_in ser_addr; memset(&ser_addr, 0 ,sizeof(ser_addr)); ser_addr.sin_family = AF_INET; ser_addr.sin_port = htons(80); ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int res = bind(sockfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); assert(res != -1); res = listen(sockfd, 5); assert(res != -1); return sockfd;}int main(){ int sockfd = socket_init(); while(1){ struct sockaddr_in cli_addr; int len = sizeof(cli_addr); int conn = accept(sockfd, (struct sockaddr*)&cli_addr, &len); if(conn < 0){ continue; } char recv_buff[512] = {0}; recv(conn, recv_buff, 511, 0); // 接收浏览器发送的数据 printf("read:\n%s\n", recv_buff); char* filename = get_filename(recv_buff); // 解析请求路径,拿到访问的文件名 if(filename == NULL){ send(conn, "ERROR!", 6, 0); close(conn); continue; } char path[128] = {"/home/shen/code"}; // 拼接请求路径 if(strcmp(filename, "/") == 0){ strcat(path, "/index.html"); }else{ strcat(path, filename); } printf("-------------------------\n"); printf("客户请求资源:%s\n", path); printf("-------------------------\n"); int file_fd = open(path, O_RDONLY); if(file_fd == -1){ send(conn, "404", 3, 0); close(conn); continue; } int file_size = lseek(file_fd, 0, SEEK_END); // 计算文件的大小 lseek(file_fd, 0, SEEK_SET); // 移回指针 char head_buff[512] = {"HTTP/1.0 200 OK\r\n"}; strcat(head_buff, "Server: my // head_buff+strlen(head_buff)就是在head_buff后面追加的意思,直接head_buff就是覆盖了 sprintf(head_buff+strlen(head_buff), "Content-Length:%d\r\n", file_size); strcat(head_buff, "\r\n"); send(conn, head_buff, strlen(head_buff), 0); // 先发头部 printf("send head:%s\n", head_buff); // 发送文件中的数据 char data_buff[1024] = {0}; int num = 0; while((num = read(file_fd, data_buff, 1024)) > 0){ send(conn, data_buff, num, 0); } close(file_fd); close(conn); } return 0;}

十三、libevent的使用

libevent就是一个封装好select、poll、epoll等功能的库

#include #include #include #include #include #include #include // event_new(base实例, 信号, 事件, 回调函数, 回调函数的参数)// evsignal_new只是帮助我们固定了信号的类型为 信号事件|永久事件// evtimer_new没有描述符,参数为-1,没有事件,参数为0#define evsignal_new(b,x,cb,arg) event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))#define evtimer_new(b,cb,arg) event_new((b), -1, 0, (cb), (arg))void signal_cb(int fd, short event, void* argc){ printf("sig=%d\n", fd);}void timeout_cb(int fd, short event, void* argc){ printf("timeout\n");}int main(){ // libevent实例,分配空间,初始化 struct event_base* base = event_init(); assert(NULL != base); // 定义事件 // struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, NULL); struct event* signal_event = event_new(base, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, NULL); assert(NULL != signal_event); // 事件添加到实例,无超时时间 event_add(signal_event, NULL); struct timeval tv = {1 ,0}; // struct event* timeout_event = evtimer_new(base, timeout_cb, NULL); struct event* timeout_event = event_new(base, -1, 0, timeout_cb, NULL); assert(NULL != timeout_event); // 设置超时事件 event_add(timeout_event, &tv); // 启动事件循环 event_base_dispatch(base); // 释放malloc分配的资源 event_free(timeout_event); event_free(signal_event); event_base_free(base); return 0;}

使用libevent写的tcp服务器

#include #include #include #include #include #include #include #include #include int socket_init(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ return -1; } struct sockaddr_in ser_addr; memset(&ser_addr, 0 ,sizeof(ser_addr)); ser_addr.sin_family = AF_INET; ser_addr.sin_port = htons(8888); ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int res = bind(sockfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); assert(res != -1); res = listen(sockfd, 5); assert(res != -1); return sockfd;}// fd描述符发生了ev事件,arg是用户给回调函数recv_cb传入的参数void recv_cb(int fd, short ev, void* arg){ if(ev & EV_READ){ char buff[128] = {0}; int n = recv(fd, buff, 127, 0); if(n <= 0){ struct event** conn_ev_ptr = (struct event**)arg; event_free(*conn_ev_ptr); // *conn_ev_ptr是event实例 free(conn_ev_ptr); // 这是手动malloc的空间 close(fd); printf("client %d close\n", fd); return; } printf("recv : %s\n", buff); send(fd, "ok", 2, 0); }}void accept_cb(int fd, short ev, void* arg){ struct event_base* base = (struct event_base*)arg; assert(NULL != base); if(ev & EV_READ){ struct sockaddr_in cli_addr; int len = sizeof(cli_addr); int conn = accept(fd, (struct sockaddr*)&cli_addr, &len); if(conn < 0){ return ; } printf("accept conn = %d\n", conn); // 给客户端建立连接后,用该描述符创建struct event,并加入libevent实例 // struct event* conn_ev = event_new(base, conn, EV_READ|EV_PERSIST, recv_cb, base); struct event** conn_ev_ptr = (struct event**)malloc(sizeof(struct event*)); assert(NULL != conn_ev_ptr); // 用libevent实例、连接和事件构造一个event,最后把这个event添加到libevent实例 // 首先用构造的event实例给*conn_ev_ptr赋值,当有读事件发生的时候,会把conn_ev_ptr传入recv_cb处理读事件 *conn_ev_ptr = event_new(base, conn, EV_READ|EV_PERSIST, recv_cb, conn_ev_ptr); assert(NULL != *conn_ev_ptr); event_add(*conn_ev_ptr, NULL); }}int main(){ // libevent实例,分配空间,初始化 struct event_base* base = event_init(); assert(NULL != base); int sockfd = socket_init(); assert(sockfd != -1); struct event* sock_ev = event_new(base, sockfd, EV_READ|EV_PERSIST, accept_cb, base); // 监听描述符添加到libevent event_add(sock_ev, NULL); event_base_dispatch(base); event_free(sock_ev); event_base_free(base); return 0;}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:虚拟地址空间和函数调用过程
下一篇:栈和队列遍历二叉树
相关文章

 发表评论

暂时没有评论,来抢沙发吧~