文章目录:
一:wrap常用函数封装
wrap.h
wrap.c
server.c封装实现
client.c封装实现
二:多进程process并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
三:多线程thread并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
一: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 类型的指针 salenptrint Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);//Bind:用于将一个网络地址(包括IP地址和端口号)绑定到一个文件描述符上//它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salenint Bind(int fd, const struct sockaddr *sa, socklen_t salen);//Connect:用于建立与远程主机的TCP连接//它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salenint Connect(int fd, const struct sockaddr *sa, socklen_t salen);//Listen:用于在服务器端创建一个TCP监听队列//它接受一个文件描述符 fd 和一个指定的最大连接等待数 backlogint Listen(int fd, int backlog);//Socket:用于创建一个新的套接字//它接受一个地址族 family,一个套接字类型 type,以及一个协议编号 protocolint Socket(int family, int type, int protocol);//Read:用于从文件中读取数据//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptr,以及要读取的最大字节数 nbytesssize_t Read(int fd, void *ptr, size_t nbytes);//Write:用于将数据写入文件//它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 ptr,以及要写入的字节数 nbytesssize_t Write(int fd, const void *ptr, size_t nbytes);//Close:用于关闭文件//它接受一个文件描述符 fdint Close(int fd);//Readn:用来读取指定数量的字节//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的字节数 nssize_t Readn(int fd, void *vptr, size_t n);//Writen:用来写入指定数量的字节//它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 vptr,以及要写入的字节数 nssize_t Writen(int fd, const void *vptr, size_t n);//my_read:用来读取数据到一个字符数组中//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptrssize_t my_read(int fd, char *ptr);//Readline:用来读取一行数据//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的最大字节数 maxlenssize_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;elseperr_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;elsereturn -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;elsereturn -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-1500ssize_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;elsereturn -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;elsereturn -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*/ //传出参数 vptrssize_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;} elsereturn -1;}*ptr = 0;return n; }
利用封装函数: 联合编译server.c 和 wrap.c 生成 server、 联合编译 client.c 和 wrap.c 生成 client
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 6666int 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); //获取本机任意有效IPserv_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 6666int main(void) {int sfd, len;struct sockaddr_in serv_addr;char buf[BUFSIZ]; //1. socket() 创建socketsfd = 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() 写数据到 socketint 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; }
read 函数的返回值
read 函数的返回值:1. > 0 实际读到的字节数2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】3. -1 应进一步判断errno的值:errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。 errno = EINTR 慢速系统调用被 中断。errno = “其他情况” 异常。
二:多进程process并发服务器
server.c服务器
实现思路
使用多进程并发服务器时要考虑以下几点:1.父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)2.系统内创建进程个数(与内存大小相关)3.进程创建过多是否降低整体服务性能(进程调度)
1. Socket(); 创建 监听套接字 lfd2. Bind() 绑定地址结构 Strcut scokaddr_in addr;3. Listen(); 4. while (1) {cfd = Accpet(); 接收客户端连接请求。pid = fork();if (pid == 0){ 子进程 read(cfd) --- 小-》大 --- write(cfd)close(lfd) 关闭用于建立连接的套接字 lfdread()小--大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;//注册信号捕捉函数: SIGCHLDsigaction(SIGCHLD, &newact, NULL);//1. Socket(); 创建 监听套接字 lfdlistenfd = 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) {//关闭用于建立连接的套接字 lfdClose(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} elseperr_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 8000int 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;}elseWrite(STDOUT_FILENO, buf, n);}Close(sockfd);return 0; }
三:多线程thread并发服务器
server.c服务器
实现思路
在使用线程模型开发服务器时需考虑以下问题:1.调整进程内最大文件描述符上限2.线程如有共享数据,考虑线程同步3.服务于客户端线程退出时,退出处理。(退出值,分离态)4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
1. Socket(); 创建 监听套接字 lfd2. 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) 不能关闭。 主线程要使用lfdread(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); //跳出循环,关闭cfdif (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(); 创建 监听套接字 lfdlistenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfdbzero(&servaddr, sizeof(servaddr)); //地址结构清零servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IPservaddr.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 8000int 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");elseWrite(STDOUT_FILENO, buf, n);}Close(sockfd);return 0; }