网站子页面设计,浦东新区网站设计,首页网站关键词优化教程,app是什么意思的缩写一、引言
C的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。
这个项目的效果是可以在浏览器通过输入网络IP地址和端口#xff0c;然后打开对应的文件目录
效果如下#xff1a; 也可以打开文件夹后点击目录#xff0c;打开到对应的文件夹…一、引言
C的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。
这个项目的效果是可以在浏览器通过输入网络IP地址和端口然后打开对应的文件目录
效果如下 也可以打开文件夹后点击目录打开到对应的文件夹中去。 这个就是简单的webserver功能后期自己也可以修改代码实现更多可能性的玩法比如做一个简单的前端交互式的界面。 二、代码开发流程
我这个项目主要用到的实现方式是用epollepoll是可以实现网络服务器编程有下面几个优点 1. 高效epoll使用事件驱动模型只有当IO事件发生时才会被激活避免了轮询的开销提高了服务器的效率。 2. 可扩展epoll支持较大的并发连接数可以处理成千上万个连接而且在连接数量增加时性能下降较慢。 3. 高可靠性epoll使用边缘触发模式只有在数据可读或可写时才会通知应用程序避免了因为网络拥塞等原因导致的误报提高了服务器的可靠性。 4. 灵活性epoll支持多种事件类型包括读、写、异常等可以根据不同的需求进行定制。 5. 跨平台epoll是Linux系统内核提供的机制可以在不同的Linux系统上使用实现跨平台开发。 下面是epoll开发webserver项目的流程图不包括具体函数的实现 int main()
{//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, //则web服务器就会收到SIGPIPE信号struct sigaction act;act.sa_handler SIG_IGN;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGPIPE, act, NULL);int lfd tcp4bind(9999,NULL);Listen(lfd,128);int epfd epoll_create(1024);if(epfd 0){perror(epoll_create error);close(lfd);return -1;}struct epoll_event ev;struct epoll_event events[1024];ev.data.fd lfd;ev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,ev);int nready;int i;int cfd;int sockfd;while(1){nready epoll_wait(epfd,events,1024,-1);if(nready 0){perror(epoll wait error);if(nready EINTR){continue;}break;}for(i 0;i nready;i ){sockfd events[i].data.fd;if(sockfd lfd){cfd Accept(lfd,NULL,NULL);//设置cfd为非阻塞防止其在 while((n Readline(cfd,buf,sizeof(buf))) 0)处阻塞 //设置cfd为非阻塞int flag fcntl(cfd, F_GETFL);flag | O_NONBLOCK;fcntl(cfd, F_SETFL, flag);ev.data.fd cfd;ev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,ev);}else {http_request(sockfd,epfd);}}}
}
上面的tcp4bind是封装好的函数如果想看具体实现可以看一下文章后面的全部代码wrap.c
代码。类似的ListenAccept也是封装好的函数。
三、http_request 函数
这个函数是具体实现打开文件和打开文件夹的函数。
当客户端发起HTTP请求时服务器会调用http_request函数来处理请求。函数流程如下
函数流程如下
读取请求行数据分析出要请求的资源文件名。判断请求的文件是否存在若不存在则发送404 NOT FOUND的头部信息和error.html文件内容。若文件存在判断文件类型如果是普通文件则发送200 OK的头部信息和文件内容如果是目录文件则发送200 OK的头部信息和目录文件列表信息的html内容。发送完数据后关闭连接并将文件描述符从epoll树上删除。
代码
int http_request(int cfd, int epfd)
{int n;char buf[1024];//读取请求行数据, 分析出要请求的资源文件名memset(buf, 0x00, sizeof(buf));n Readline(cfd, buf, sizeof(buf));if(n0){//printf(read error or client closed, n[%d]\n, n);//关闭连接close(cfd);//将文件描述符从epoll树上删除epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1; }printf(buf[%s]\n, buf);//GET /hanzi.c HTTP/1.1char reqType[16] {0};char fileName[255] {0};char protocal[16] {0};sscanf(buf, %[^ ] %[^ ] %[^ \r\n], reqType, fileName, protocal);//printf([%s]\n, reqType);printf(--[%s]--\n, fileName);//printf([%s]\n, protocal);char *pFile fileName;if(strlen(fileName)1){strcpy(pFile, ./);}else{pFile fileName1;}//转换汉字编码strdecode(pFile, pFile);printf([%s]\n, pFile);//循环读取完剩余的数据,避免产生粘包while((nReadline(cfd, buf, sizeof(buf)))0);//判断文件是否存在struct stat st;if(stat(pFile, st)0){printf(file not exist\n);//发送头部信息send_header(cfd, 404, NOT FOUND, get_mime_type(.html), 0);//发送文件内容send_file(cfd, error.html); }else //若文件存在{//判断文件类型//普通文件if(S_ISREG(st.st_mode)) //man 2 stat查询S_ISREG表示普通文件 {printf(file exist\n);//发送头部信息send_header(cfd, 200, OK, get_mime_type(pFile), st.st_size);//发送文件内容send_file(cfd, pFile);}//目录文件else if(S_ISDIR(st.st_mode)){printf(目录文件\n);char buffer[1024];//发送头部信息send_header(cfd, 200, OK, get_mime_type(.html), 0); //发送html文件头部send_file(cfd, html/dir_header.html); //文件列表信息struct dirent **namelist;int num;num scandir(pFile, namelist, NULL, alphasort);if (num 0){perror(scandir);close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1;}else{while (num--) {printf(%s\n, namelist[num]-d_name);memset(buffer, 0x00, sizeof(buffer));if(namelist[num]-d_typeDT_DIR){sprintf(buffer, lia href%s/%s/a/li, namelist[num]-d_name, namelist[num]-d_name);}else{sprintf(buffer, lia href%s%s/a/li, namelist[num]-d_name, namelist[num]-d_name);}free(namelist[num]);Write(cfd, buffer, strlen(buffer));}free(namelist);}//发送html尾部sleep(10);send_file(cfd, html/dir_tail.html); }}return 0;
}
四、细节 1.cfd要设置为非阻塞
//设置cfd为非阻塞int flag fcntl(cfd, F_GETFL);flag | O_NONBLOCK;fcntl(cfd, F_SETFL, flag);
这段代码的作用是将文件描述符cfd设置为非阻塞模式。
首先使用fcntl函数和F_GETFL命令获取cfd的文件状态标志。这些标志包括文件的读写模式、是否阻塞等信息。获取后的标志保存在flag变量中。
接着使用按位或运算符(|)将O_NONBLOCK标志表示非阻塞模式添加到flag变量中。这样做是为了将O_NONBLOCK标志添加到文件描述符的状态标志中表示将该文件描述符设置为非阻塞模式。
最后使用fcntl函数和F_SETFL命令将修改后的flag标志设置回文件描述符cfd以实现将cfd设置为非阻塞模式。
因此这段代码的作用是将文件描述符cfd设置为非阻塞模式以便在进行I/O操作时如果没有数据可读或没有足够的空间可写不会阻塞进程的执行而是立即返回一个错误或一个特殊的状态使得进程可以继续执行其他任务。
2.要改变环境工作目录
前提是把webpath设置在家目录下
char path[255] {0};
sprintf(path, %s/%s, getenv(HOME), webpath);
chdir(path);
这段代码的作用是构造一个路径并将当前工作目录切换到该路径。
让我们逐步解释这段代码 char path[255] {0}; - 定义一个长度为255的字符数组path并初始化为0。这个数组将用来存储构造的路径。 sprintf(path, %s/%s, getenv(HOME), webpath); - 使用sprintf函数将路径构造为$HOME/webpath的形式。getenv(HOME)用于获取当前用户的主目录路径然后将其与webpath拼接起来得到完整的路径。 chdir(path); - 使用chdir函数将当前工作目录切换到构造的路径。这样程序的当前工作目录就会变成$HOME/webpath。
综合起来这段代码的作用是构造一个路径并将当前工作目录切换到该路径。通常情况下这样的操作用于确保程序在正确的目录下执行以便正确地访问和处理文件。 3.fileName 读取位置1略过“/“
不然就是下面这样 4.scandir函数
scandir 函数是用于扫描指定目录并返回目录中的文件列表的函数。它返回一个指向 dirent 结构的指针数组每个结构包含一个目录中的一个条目的信息。
以下是 scandir 函数的原型
int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));
dirp要扫描的目录的路径名。
namelist指向指针数组的指针用于存储指向每个目录条目的指针。
filter一个可选的过滤函数用于决定哪些目录条目应该被返回。如果不需要过滤可以将其设置为 NULL。
compar一个可选的比较函数用于对返回的目录条目进行排序。如果不需要排序可以将其设置为 NULL。
以下是一个简单的示例演示了如何使用 scandir 函数来列出目录中的文件
#include stdio.h
#include stdlib.h
#include dirent.hint main() {struct dirent **namelist;int n;n scandir(., namelist, NULL, alphasort);if (n 0) {perror(scandir);exit(EXIT_FAILURE);} else {for (int i 0; i n; i) {printf(%s\n, namelist[i]-d_name);free(namelist[i]);}free(namelist);}return 0;
}
在这个示例中scandir 函数扫描当前目录并使用 alphasort 函数对返回的文件列表进行排序。然后它遍历列表并打印每个文件的名称。 5.添加默认路径
比如http://192.168.44.3:9999 可以访问默认的主目录下面的文件夹内容
char *pFile fileName;
if(strlen(fileName)1) //添加默认为主目录下面
{strcpy(pFile, ./);
}
else
{pFile fileName1;
}
注意不能将char *pFile fileName NULL 设置为这样否则会产生段错误 6.解决遇到汉字的问题
在webserver代码中调用了一个函数
strdecode(pFile, pFile); 这个函数在pub.c中然后写了一个编码用作回写浏览器的时候将除字母数字及/_.-~以外的字符转义后回写。 //strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{int tolen;for (tolen 0; *from ! \0 tolen 4 tosize; from) {if (isalnum(*from) || strchr(/_.-~, *from) ! (char*)0) {*to *from;to;tolen;} else {sprintf(to, %%%02x, (int) *from 0xff);to 3;tolen 3;}}*to \0;
} 5.对 SIGPIPE 信号的处理方式设置为忽略
对 SIGPIPE 信号的处理方式设置为忽略即当进程收到 SIGPIPE 信号时不做任何处理。这通常用于避免在网络编程中出现 SIGPIPE 错误因为当一个进程向一个已经关闭的 socket 发送数据时系统会向该进程发送 SIGPIPE 信号如果不处理该信号进程会终止。通过将 SIGPIPE 信号的处理方式设置为忽略可以避免进程因此而终止。
//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接,
//则web服务器就会收到SIGPIPE信号
struct sigaction act;
act.sa_handler SIG_IGN;
sigemptyset(act.sa_mask);
act.sa_flags 0;
sigaction(SIGPIPE, act, NULL); 五、完整代码
webserver.c
//web服务端程序--使用epoll模型
#include unistd.h
#include sys/epoll.h
#include fcntl.h
#include sys/stat.h
#include string.h
#include signal.h
#include dirent.h#include pub.h
#include wrap.hint http_request(int cfd, int epfd);int main()
{//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, //则web服务器就会收到SIGPIPE信号struct sigaction act;act.sa_handler SIG_IGN;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGPIPE, act, NULL);//改变当前进程的工作目录char path[255] {0};sprintf(path, %s/%s, getenv(HOME), webpath);chdir(path);//创建socket--设置端口复用---bindint lfd tcp4bind(9999, NULL);//设置监听 Listen(lfd, 128);//创建epoll树int epfd epoll_create(1024);if(epfd0){perror(epoll_create error);close(lfd);return -1;}//将监听文件描述符lfd上树struct epoll_event ev;ev.data.fd lfd;ev.events EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, ev);int i;int cfd;int nready;int sockfd;struct epoll_event events[1024];while(1){//等待事件发生nready epoll_wait(epfd, events, 1024, -1);if(nready0){if(errnoEINTR){continue;}break;}for(i0; inready; i){sockfd events[i].data.fd;//有客户端连接请求if(sockfdlfd){//接受新的客户端连接cfd Accept(lfd, NULL, NULL);//设置cfd为非阻塞int flag fcntl(cfd, F_GETFL);flag | O_NONBLOCK;fcntl(cfd, F_SETFL, flag);//将新的cfd上树ev.data.fd cfd;ev.events EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, ev);}else{//有客户端数据发来http_request(sockfd, epfd);} } }
}int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{char buf[1024] {0};sprintf(buf, HTTP/1.1 %s %s\r\n, code, msg);sprintf(bufstrlen(buf), Content-Type:%s\r\n, fileType);if(len0){sprintf(bufstrlen(buf), Content-Length:%d\r\n, len);}strcat(buf, \r\n);Write(cfd, buf, strlen(buf));return 0;
}int send_file(int cfd, char *fileName)
{//打开文件int fd open(fileName, O_RDONLY);if(fd0){perror(open error);return -1;}//循环读文件, 然后发送int n;char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));n read(fd, buf, sizeof(buf));if(n0){break;}else{Write(cfd, buf, n);}}
}int http_request(int cfd, int epfd)
{int n;char buf[1024];//读取请求行数据, 分析出要请求的资源文件名memset(buf, 0x00, sizeof(buf));n Readline(cfd, buf, sizeof(buf));if(n0){//printf(read error or client closed, n[%d]\n, n);//关闭连接close(cfd);//将文件描述符从epoll树上删除epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1; }printf(buf[%s]\n, buf);//GET /hanzi.c HTTP/1.1char reqType[16] {0};char fileName[255] {0};char protocal[16] {0};sscanf(buf, %[^ ] %[^ ] %[^ \r\n], reqType, fileName, protocal);//printf([%s]\n, reqType);printf(--[%s]--\n, fileName);//printf([%s]\n, protocal);char *pFile fileName;if(strlen(fileName)1){strcpy(pFile, ./);}else{pFile fileName1;}//转换汉字编码strdecode(pFile, pFile);printf([%s]\n, pFile);//循环读取完剩余的数据,避免产生粘包while((nReadline(cfd, buf, sizeof(buf)))0);//判断文件是否存在struct stat st;if(stat(pFile, st)0){printf(file not exist\n);//发送头部信息send_header(cfd, 404, NOT FOUND, get_mime_type(.html), 0);//发送文件内容send_file(cfd, error.html); }else //若文件存在{//判断文件类型//普通文件if(S_ISREG(st.st_mode)) //man 2 stat查询S_ISREG表示普通文件 {printf(file exist\n);//发送头部信息send_header(cfd, 200, OK, get_mime_type(pFile), st.st_size);//发送文件内容send_file(cfd, pFile);}//目录文件else if(S_ISDIR(st.st_mode)){printf(目录文件\n);char buffer[1024];//发送头部信息send_header(cfd, 200, OK, get_mime_type(.html), 0); //发送html文件头部send_file(cfd, html/dir_header.html); //文件列表信息struct dirent **namelist;int num;num scandir(pFile, namelist, NULL, alphasort);if (num 0){perror(scandir);close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1;}else{while (num--) {printf(%s\n, namelist[num]-d_name);memset(buffer, 0x00, sizeof(buffer));if(namelist[num]-d_typeDT_DIR){sprintf(buffer, lia href%s/%s/a/li, namelist[num]-d_name, namelist[num]-d_name);}else{sprintf(buffer, lia href%s%s/a/li, namelist[num]-d_name, namelist[num]-d_name);}free(namelist[num]);Write(cfd, buffer, strlen(buffer));}free(namelist);}//发送html尾部sleep(10);send_file(cfd, html/dir_tail.html); }}return 0;
} pub.c
#include pub.h
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{char* dot;dot strrchr(name, .); //自右向左查找‘.’字符;如不存在返回NULL/**charsetiso-8859-1 西欧的编码说明网站采用的编码是英文*charsetgb2312 说明网站采用的编码是简体中文*charsetutf-8 代表世界通用的语言编码* 可以用到中文、韩文、日文等世界上所有语言编码上*charseteuc-kr 说明网站采用的编码是韩文*charsetbig5 说明网站采用的编码是繁体中文**以下是依据传递进来的文件名使用后缀判断是何种文件类型*将对应的文件类型按照http定义的关键字发送回去*/if (dot (char*)0)return text/plain; charsetutf-8;if (strcmp(dot, .html) 0 || strcmp(dot, .htm) 0)return text/html; charsetutf-8;if (strcmp(dot, .jpg) 0 || strcmp(dot, .jpeg) 0)return image/jpeg;if (strcmp(dot, .gif) 0)return image/gif;if (strcmp(dot, .png) 0)return image/png;if (strcmp(dot, .css) 0)return text/css;if (strcmp(dot, .au) 0)return audio/basic;if (strcmp( dot, .wav) 0)return audio/wav;if (strcmp(dot, .avi) 0)return video/x-msvideo;if (strcmp(dot, .mov) 0 || strcmp(dot, .qt) 0)return video/quicktime;if (strcmp(dot, .mpeg) 0 || strcmp(dot, .mpe) 0)return video/mpeg;if (strcmp(dot, .vrml) 0 || strcmp(dot, .wrl) 0)return model/vrml;if (strcmp(dot, .midi) 0 || strcmp(dot, .mid) 0)return audio/midi;if (strcmp(dot, .mp3) 0)return audio/mpeg;if (strcmp(dot, .ogg) 0)return application/ogg;if (strcmp(dot, .pac) 0)return application/x-ns-proxy-autoconfig;return text/plain; charsetutf-8;
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,* carriage return, or a CRLF combination. Terminates the string read* with a null character. If no newline indicator is found before the* end of the buffer, the string is terminated with a null. If any of* the above three line terminators is read, the last character of the* string will be a linefeed and the string will be terminated with a* null character.* Parameters: the socket descriptor* the buffer to save the data in* the size of the buffer* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{int i 0;char c \0;int n;while ((i size - 1) (c ! \n)){n recv(sock, c, 1, 0);/* DEBUG printf(%02X\n, c); */if (n 0){if (c \r){n recv(sock, c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据但是数据不从缓冲区清除/* DEBUG printf(%02X\n, c); */if ((n 0) (c \n))recv(sock, c, 1, 0);elsec \n;}buf[i] c;i;}elsec \n;}buf[i] \0;return(i);
}//下面的函数第二天使用
/** 这里的内容是处理%20之类的东西是解码过程。* %20 URL编码中的‘ ’(space)* %21 ! %22 %23 # %24 $* %25 % %26 %27 %28 (......* 相关知识html中的‘ ’(space)是 */
void strdecode(char *to, char *from)
{for ( ; *from ! \0; to, from) {if (from[0] % isxdigit(from[1]) isxdigit(from[2])) { //依次判断from中 %20 三个字符*to hexit(from[1])*16 hexit(from[2]);//字符串E8变成了真正的16进制的E8from 2; //移过已经处理的两个字符(%21指针指向1),表达式3的from还会再向后移一个字符} else*to *from;}*to \0;
}//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{if (c 0 c 9)return c - 0;if (c a c f)return c - a 10;if (c A c F)return c - A 10;return 0;
}//编码用作回写浏览器的时候将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{int tolen;for (tolen 0; *from ! \0 tolen 4 tosize; from) {if (isalnum(*from) || strchr(/_.-~, *from) ! (char*)0) {*to *from;to;tolen;} else {sprintf(to, %%%02x, (int) *from 0xff);to 3;tolen 3;}}*to \0;
}
wrap.c
#include stdlib.h
#include stdio.h
#include unistd.h
#include errno.h
#include string.h
#include sys/socket.h
#include arpa/inet.h
#include strings.h
//绑定错误显示和退出
void perr_exit(const char *s)
{perror(s);exit(-1);
}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))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断goto again;elseperr_exit(accept error);}return n;
}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;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n connect(fd, sa, salen)) 0)perr_exit(connect error);return n;
}int Listen(int fd, int backlog)
{int n;if ((n listen(fd, backlog)) 0)perr_exit(listen error);return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n socket(family, type, protocol)) 0)perr_exit(socket error);return n;
}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;
}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;
}int Close(int fd)
{int n;if ((n close(fd)) -1)perr_exit(close error);return n;
}/*参三: 应该读取的字节数*/
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;while (nleft 0) {if ((nread read(fd, ptr, nleft)) 0) {if (errno EINTR)nread 0;elsereturn -1;} else if (nread 0)break;nleft - nread;//防止一次数据没有读完ptr nread;//指针需要向后移动}return n - nleft;
}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;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];//定义了100的缓冲区if (read_cnt 0) {
again://使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率if ( (read_cnt read(fd, read_buf, sizeof(read_buf))) 0) {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;
}
//读取一行
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 c;if (c \n)//代表任务完成break;} else if (rc 0) {//对端关闭*ptr 0;//0 \0return n - 1;} elsereturn -1;}*ptr 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd Socket(AF_INET,SOCK_STREAM,0);bzero(serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()if(IP NULL){//如果这样使用 0.0.0.0,任意ip将可以连接serv_addr.sin_addr.s_addr INADDR_ANY;}else{if(inet_pton(AF_INET,IP,serv_addr.sin_addr.s_addr) 0){perror(IP);//转换失败exit(1);}}serv_addr.sin_family AF_INET;serv_addr.sin_port htons(port);int opt 1;setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));Bind(lfd,(struct sockaddr *)serv_addr,sizeof(serv_addr));return lfd;
}
完整项目包上篇文章有自取。感谢支持