文章目录:
一:wrap常用函数封装
wrap.h
wrap.c
server.c封装实现
client.c封装实现
二:多进程process并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
三:多线程thread并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
read 函数的返回值文章来源:https://www.toymoban.com/news/detail-662470.html
read 函数的返回值: 1. > 0 实际读到的字节数 2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】 3. -1 应进一步判断errno的值: errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。 errno = EINTR 慢速系统调用被 中断。 errno = “其他情况” 异常。
一:wrap常用函数封装
wrap.h
//声明了一些网络编程中常用的函数 //所有的函数都放在条件编译的代码块中,这样只有在编译时定义了 __WRAP_H_ 这个宏 #ifndef __WRAP_H_ #define __WRAP_H_ //perr_exit:用来处理错误并退出程序 void perr_exit(const char *s); //Accept:用于接受一个TCP连接请求 //它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及一个指向 socklen_t 类型的指针 salenptr int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); //Bind:用于将一个网络地址(包括IP地址和端口号)绑定到一个文件描述符上 //它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salen int Bind(int fd, const struct sockaddr *sa, socklen_t salen); //Connect:用于建立与远程主机的TCP连接 //它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salen int Connect(int fd, const struct sockaddr *sa, socklen_t salen); //Listen:用于在服务器端创建一个TCP监听队列 //它接受一个文件描述符 fd 和一个指定的最大连接等待数 backlog int Listen(int fd, int backlog); //Socket:用于创建一个新的套接字 //它接受一个地址族 family,一个套接字类型 type,以及一个协议编号 protocol int Socket(int family, int type, int protocol); //Read:用于从文件中读取数据 //它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptr,以及要读取的最大字节数 nbytes ssize_t Read(int fd, void *ptr, size_t nbytes); //Write:用于将数据写入文件 //它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 ptr,以及要写入的字节数 nbytes ssize_t Write(int fd, const void *ptr, size_t nbytes); //Close:用于关闭文件 //它接受一个文件描述符 fd int Close(int fd); //Readn:用来读取指定数量的字节 //它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的字节数 n ssize_t Readn(int fd, void *vptr, size_t n); //Writen:用来写入指定数量的字节 //它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 vptr,以及要写入的字节数 n ssize_t Writen(int fd, const void *vptr, size_t n); //my_read:用来读取数据到一个字符数组中 //它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptr ssize_t my_read(int fd, char *ptr); //Readline:用来读取一行数据 //它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的最大字节数 maxlen ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
wrap.c
//这个头文件包含了标准库的函数和变量,包括用于内存分配、输入/输出、错误处理等功能的函数和变量 #include <stdlib.h> //这个头文件包含了标准输入/输出库的函数和变量,包括用于文件操作、标准输入/输出等功能的函数和变量 #include <stdio.h> //这个头文件包含了用于Unix和类Unix系统中的函数和变量,包括与进程控制、系统调用等相关的函数和变量 #include <unistd.h> //这个头文件包含了用于错误处理的函数和变量,包括用于表示错误码的宏和全局变量 #include <errno.h> //这个头文件包含了用于网络编程的函数和变量,包括用于创建、操作套接字等功能的函数和变量 #include <sys/socket.h> //perr_exit:用来处理错误并退出程序 void perr_exit(const char *s) { perror(s); exit(-1); } //Accept:用于接受一个TCP连接请求 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ((n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } //Bind:用于将一个网络地址(包括IP地址和端口号)绑定到一个文件描述符上 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } //Connect:用于建立与远程主机的TCP连接 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; n = connect(fd, sa, salen); if (n < 0) { perr_exit("connect error"); } return n; } //Listen:用于在服务器端创建一个TCP监听队列 int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } //Socket:用于创建一个新的套接字 int Socket(int family, int type, int protocol) { int n; if ((n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } //Read:用于从文件中读取数据 ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } //Write:用于将数据写入文件 ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ((n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } //Close:用于关闭文件 int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } //Readn:用来读取指定数量的字节 /*参三: 应该读取的字节数 读 N 个字节*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500 ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; //usigned int 剩余未读取的字节数 ssize_t nread; //int 实际读到的字节数 char *ptr; ptr = vptr; nleft = n; //n 未读取字节数 while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; //nleft = nleft - nread ptr += nread; } return n - nleft; } //Writen:用来写入指定数量的字节 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 (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } //my_read:用来读取数据到一个字符数组中 static ssize_t my_read(int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { //"hello\n" if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } //Readline:用来读取一行数据 /*readline读一行 --- fgets*/ //传出参数 vptr 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++) { if ((rc = my_read(fd, &c)) == 1) { //ptr[] = hello\n *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n-1; } else return -1; } *ptr = 0; return n; }
利用封装函数: 联合编译server.c 和 wrap.c 生成 server、 联合编译 client.c 和 wrap.c 生成 client文章来源地址https://www.toymoban.com/news/detail-662470.html
server.c封装实现
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #include "wrap.h" #define SERV_PORT 6666 int main(void) { int sfd, cfd; int len, i; char buf[BUFSIZ], clie_IP[BUFSIZ]; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; /*1.socket函数:创建用于建立连接的socket,返回的文件描述符存入link_fd*/ sfd = Socket(AF_INET, SOCK_STREAM, 0); int opt = 1; //设置套接字选项:文件描述符,要设置的选项是套接字的选项,要设置的选项是“重用地址”选项,包含了要设置的选项的值,选项值的长度 setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&serv_addr, sizeof(serv_addr)); //将指定内存区域的内容初始化为0 serv_addr.sin_family = AF_INET; //IPv4 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //获取本机任意有效IP serv_addr.sin_port = htons(SERV_PORT); //转为网络字节序的 端口号 /*2.bind函数:绑定服务器端的socket绑定地址结构(IP+port)*/ Bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); /*3.listen函数:设定监听(连接)上线*/ Listen(sfd, 2); printf("wait for client connect ...\n"); clie_addr_len = sizeof(clie_addr_len); /*4.accept函数:阻塞等待客户端建立连接*/ cfd = Accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /*建立连接后打印客户端的IP和端口号 获取客户端地址结构*/ printf("cfd = ----%d\n", cfd); printf("client IP: %s port:%d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); while (1) { //5. read(fd) 读socket获取客户端数据 len = Read(cfd, buf, sizeof(buf)); Write(STDOUT_FILENO, buf, len); //6. 小--大写 toupper() for (i = 0; i < len; i++) buf[i] = toupper(buf[i]); //7. write(fd) Write(cfd, buf, len); } //8. close() Close(sfd); Close(cfd); return 0; }
client.c封装实现
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include "wrap.h" #define SERV_IP "127.0.0.1" #define SERV_PORT 6666 int main(void) { int sfd, len; struct sockaddr_in serv_addr; char buf[BUFSIZ]; //1. socket() 创建socket sfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); //将指定内存区域的内容初始化为0 serv_addr.sin_family = AF_INET; //IPv4 inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //本地字节序(string IP) ---> 网络字节序 serv_addr.sin_port = htons(SERV_PORT); //转为网络字节序的 端口号 //2. connect(); 与服务器建立连接 Connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); while (1) { fgets(buf, sizeof(buf), stdin); //3. write() 写数据到 socket int r = Write(sfd, buf, strlen(buf)); printf("Write r ======== %d\n", r); //4. read() 读转换后的数据 len = Read(sfd, buf, sizeof(buf)); printf("Read len ========= %d\n", len); //5. 显示读取结果 Write(STDOUT_FILENO, buf, len); } //6. close() Close(sfd); return 0; }
二:多进程process并发服务器
server.c服务器
实现思路
使用多进程并发服务器时要考虑以下几点: 1.父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) 2.系统内创建进程个数(与内存大小相关) 3.进程创建过多是否降低整体服务性能(进程调度)
1. Socket(); 创建 监听套接字 lfd 2. Bind() 绑定地址结构 Strcut scokaddr_in addr; 3. Listen(); 4. while (1) { cfd = Accpet(); 接收客户端连接请求。 pid = fork(); if (pid == 0){ 子进程 read(cfd) --- 小-》大 --- write(cfd) close(lfd) 关闭用于建立连接的套接字 lfd read() 小--大 write() } else if (pid > 0) { close(cfd); 关闭用于与客户端通信的套接字 cfd contiue; } } 5. 子进程: close(lfd) read() 小--大 write() 父进程: close(cfd); 注册信号捕捉函数: SIGCHLD 在回调函数中, 完成子进程回收 while (waitpid());
代码逻辑
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <ctype.h> #include <unistd.h> #include "wrap.h" #define MAXLINE 8192 #define SERV_PORT 8000 //在回调函数中, 完成子进程回收 void do_sigchild(int num) { while (waitpid(0, NULL, WNOHANG) > 0) ; } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; pid_t pid; struct sigaction newact; //5.父进程 newact.sa_handler = do_sigchild; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; //注册信号捕捉函数: SIGCHLD sigaction(SIGCHLD, &newact, NULL); //1. Socket(); 创建 监听套接字 lfd listenfd = Socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); //2. Bind() 绑定地址结构 Strcut scokaddr_in addr; Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //3. Listen(); Listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); //accept() 阻塞监听客户端连接,接收客户端连接请求 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("-------------------------%d\n", connfd); pid = fork(); //4.子进程 read(connfd) --- 小-》大 --- write(connfd) if (pid == 0) { //关闭用于建立连接的套接字 lfd Close(listenfd); while (1) { //read() n = Read(connfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); //小--大 for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); //write() Write(STDOUT_FILENO, buf, n); Write(connfd, buf, n); } Close(connfd); return 0; } else if (pid > 0) { Close(connfd); //关闭用于与客户端通信的套接字 connfd } else perr_exit("fork"); } return 0; }
client.c客户端
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h" #define MAXLINE 8192 #define SERV_PORT 8000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
三:多线程thread并发服务器
server.c服务器
实现思路
在使用线程模型开发服务器时需考虑以下问题: 1.调整进程内最大文件描述符上限 2.线程如有共享数据,考虑线程同步 3.服务于客户端线程退出时,退出处理。(退出值,分离态) 4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
1. Socket(); 创建 监听套接字 lfd 2. Bind() 绑定地址结构 Strcut scokaddr_in addr; 3. Listen(); 4. while (1) { cfd = Accept(lfd, ); pthread_create(&tid, NULL, tfn, (void *)cfd); pthread_detach(tid); // pthead_join(tid, void **); 新线程---专用于回收子线程 } 5. 子线程: void *tfn(void *arg) { // close(lfd) 不能关闭。 主线程要使用lfd read(cfd) 小--大 write(cfd) pthread_exit((void *)10); }
代码逻辑
#include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> #include <ctype.h> #include <unistd.h> #include <fcntl.h> #include "wrap.h" #define MAXLINE 8192 #define SERV_PORT 8000 //定义一个结构体, 将地址结构跟cfd捆绑 struct s_info { struct sockaddr_in cliaddr; int connfd; }; //子线程 void *do_work(void *arg) { int n,i; struct s_info *ts = (struct s_info*)arg; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看 while (1) { //读客户端 //read(cfd) n = Read(ts->connfd, buf, MAXLINE); //跳出循环,关闭cfd if (n == 0) { printf("the client %d closed...\n", ts->connfd); break; } //打印客户端信息(IP/PORT) printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port)); //小写-->大写 for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); //写出至屏幕 Write(STDOUT_FILENO, buf, n); //回写给客户端 Write(ts->connfd, buf, n); } Close(ts->connfd); return (void *)0; } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; pthread_t tid; struct s_info ts[256]; //创建结构体数组. int i = 0; //1. Socket(); 创建 监听套接字 lfd listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd bzero(&servaddr, sizeof(servaddr)); //地址结构清零 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP servaddr.sin_port = htons(SERV_PORT); //指定端口号 //2. Bind() 绑定地址结构 Strcut scokaddr_in addr; Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定 //3. Listen(); Listen(listenfd, 128); //设置同一时刻链接服务器上限数 printf("Accepting client connect ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); //accept() 阻塞监听客户端连接 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求 ts[i].cliaddr = cliaddr; ts[i].connfd = connfd; pthread_create(&tid, NULL, do_work, (void*)&ts[i]); pthread_detach(tid); //子线程分离,防止僵线程产生. i++; } return 0; }
client.c客户端
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 8000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
到了这里,关于Linux网络编程:多进程 多线程_并发服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!