南通网站建设贵吗,外贸自建站有哪些,wordpress 文章底部作者,h5页面制作网站免费在上节已经系统介绍了大致的流程和相关的API#xff0c;这节就开始写代码#xff01; 回顾上节的流程#xff1a; 创建一个NET文件夹 来存放网络编程相关的代码#xff1a;
tcp服务端代码初步实现--上
这部分先实现服务器的连接部分的代码并进行验证
server1.c#xff…在上节已经系统介绍了大致的流程和相关的API这节就开始写代码 回顾上节的流程 创建一个NET文件夹 来存放网络编程相关的代码
tcp服务端代码初步实现--上
这部分先实现服务器的连接部分的代码并进行验证
server1.c
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include linux/in.h
#include string.hint main()
{int sockfd;int conn_sockfd;int ret;struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(my_addr,0,sizeof(struct sockaddr_in));memset(client_addr,0,sizeof(struct sockaddr_in));//socketsockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return 1;}else{printf(socket success, sockfd %d\n,sockfd);}//bindmy_addr.sin_family AF_INET;my_addr.sin_port htons(8888);//host to net (2 bytes)inet_aton(192.168.20.137,my_addr.sin_addr); //char* format - net formatret bind(sockfd, (struct sockaddr *)my_addr, sizeof(struct sockaddr_in));if(ret -1){perror(bind);return 1;}else{printf(bind success\n);}//listenret listen(sockfd,10);if(ret -1){perror(listen);return 1;}else{printf(listening...\n);}//acceptint len sizeof(struct sockaddr_in);conn_sockfd accept(sockfd,(struct sockaddr *)client_addr,len);if(conn_sockfd -1){perror(accept);return 1;}else{printf(accept success, client IP %s\n,inet_ntoa(client_addr.sin_addr));//将网络格式的IP地址再转回字符串}//read//write//read//closereturn 0;
}
代码验证
先编译并运行这部分代码 可见此时没有客户端进行连接程序会阻塞在监听的阶段
此时打开windows的cmdwindows系统和linux虚拟机的系统可以看作两台不同的终端 telnet指令使用的也是TCP协议 执行这条命令后windows的cmd变成了这样 再反观linux虚拟机 使用windows的ipconfig可以验证IP地址 所以连接部分的代码已经成功只是因为没有接下来的数据传输所以退出了。 tcp服务端代码初步实现--下
这部分实现服务器的连接成功后的读写并验证
server1.c
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include linux/in.h
#include string.hint main()
{int sockfd;int conn_sockfd;int ret;int n_read;int n_write;char readbuf[1024];struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(my_addr,0,sizeof(struct sockaddr_in));memset(client_addr,0,sizeof(struct sockaddr_in));//socketsockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return 1;}else{printf(socket success, sockfd %d\n,sockfd);}//bindmy_addr.sin_family AF_INET;my_addr.sin_port htons(8888);//host to net (2 bytes)inet_aton(192.168.20.137,my_addr.sin_addr); //char* format - net formatret bind(sockfd, (struct sockaddr *)my_addr, sizeof(struct sockaddr_in));if(ret -1){perror(bind);return 1;}else{printf(bind success\n);}//listenret listen(sockfd,10);if(ret -1){perror(listen);return 1;}else{printf(listening...\n);}//acceptint len sizeof(struct sockaddr_in);conn_sockfd accept(sockfd,(struct sockaddr *)client_addr,len);if(conn_sockfd -1){perror(accept);return 1;}else{printf(accept success, client IP %s\n,inet_ntoa(client_addr.sin_addr));}//readn_read read(conn_sockfd,readbuf,1024);if(n_read -1){perror(read);return 1;}else{printf(%d bytes has been read, context %s\n,n_read,readbuf);}//writechar *msg this is server, I have received your msg\n;n_write write(conn_sockfd,msg,strlen(msg));if(n_write -1){perror(write);return 1;}else{printf(%d bytes has been written\n,n_write);}//read//closereturn 0;
}
代码验证
这部分如果用windows的telnet打一个符号就会直接结束所以用Linux另开一个cmd使用telnet来模拟服务器和客户端的对话
还是先运行代码 然后运行telnet 反观服务端 在连接成功后客户端输入一句话然后回车 客户端显示 服务器显示 可见数据的交互基本没什么问题。
至此构建了一个大致的服务器代码框架可以开始着手编写客户端的代码了。 客户端代码初步实现
client.c
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include linux/in.h
#include string.hint main()
{int sockfd;int ret;int n_read;int n_write;char readbuf[1024];struct sockaddr_in server_addr;memset(server_addr,0,sizeof(struct sockaddr_in));//socketsockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return 1;}else{printf(socket success, sockfd %d\n,sockfd);}//connectserver_addr.sin_family AF_INET;server_addr.sin_port htons(8888);//host to net (2 bytes)inet_aton(192.168.20.137,server_addr.sin_addr); ret connect(sockfd, (struct sockaddr *)server_addr, sizeof(struct sockaddr_in));if(ret -1){perror(connect);return 1;}else{printf(connect success!\n);}//writechar *msg client: hello\n;n_write write(sockfd,msg,strlen(msg));if(n_write -1){perror(write);return 1;}else{printf(%d bytes has been written\n,n_write);}//readn_read read(sockfd,readbuf,1024);if(n_read -1){perror(read);return 1;}else{printf(%d bytes has been read, context %s\n,n_read,readbuf);}//closereturn 0;
}
代码验证
先编译并允许服务端 再编译并运行客户端 回看服务端 客户端 服务端 代码的最终实现
之前已经大致编写出了客户端和服务端的代码但是和本节开头的框图对比发现还差一些首先数据的发送和读取应该持续进行直到收到结束信号另外在最后要编写关闭套接字关闭连接的代码且服务器应该可以接受多个客户端的数据还有一些细节的优化 回顾之前关于fork函数的一节进程的创建_mjmmm的博客-CSDN博客 所以此处可以使用fork函数 小知识点自动对齐ggG 参考linux代码对齐快捷键和man帮助文档的使用总结_陌上花开缓缓归以的博客-CSDN博客 在命令模式下即非“插入”等编辑模式先输入gg这时候光标会移动到第一行第一个字符然后按 “” 号之后切换成大写再按一下G这时候光标会移到最后一行的第一个字符这时候就可以看到代码被排得整整齐齐了 “gg将光标移动到代码首部,”表示对齐指令,G表示代码尾部,所以执行ggG后,该文件的所有代码都将对齐 实现思路
服务端 在listen之后进入一个while(1)循环并调用accept阻塞一旦连接到一个客户端就fork一个子进程来处理数据父进程则继续通过while(1)继续调用accept阻塞等待下一个客户端连接。 而子进程中再次调用fork父进程写一个while(1)不断的写数据子进程写一个while(1)不断的读数据。 客户端 在connect之后进行fork父进程写一个while(1)不断的写数据子进程写一个while(1)不断的读数据。 如何退出 我希望的退出方式是客户端输入“quit”就会退出但是不管是客户端还是服务端为了读和写不会相互阻塞都在不同的进程中的while(1)里当客户端输入“quit”之后只有客户端的写端和服务器的读端知道客户端的读端和服务器的写端并不知情所以需要使用进程间的通讯此处我使用了FIFO 在客户端中创建FIFO并在不断写数据的父进程中不断检测是否输入了“quit”如果是就只写打开fifo并阻塞等待....一旦等待到了有进程只读打开FIFO就会往FIFO写入“quit”然后关闭FIFO关闭套接字收集子进程退出状态然后退出循环正常退出。 同时在不断读数据的子进程中不断非阻塞的只读打开fifo并每次都将光标移到最开头一旦从FIFO读取到了“quit”就exit。 但是不断读数据的子进程会阻塞读取服务器传来写入的数据这就导致当客户端输入“quit”之后无法立刻退出而是要等到服务器再发来消息才能进行下一轮的FIFO读取才能使得子进程收到父进程通过FIFO发来的“quit”并退出。解决办法就是在服务器端中一旦检测到客户端发来的消息是quit之后就立刻给客户端发送一句话 此时对于客户端来说输入了“quit”之后会立刻退出但是服务端只有读端的while可以退出写端的while无法退出此时就有一个疑问“我在读端关闭了客户端套接字照理说写端应该往这个套接字里写会报错我直接在报错处理函数里退出写端不就行了”但其实这是行不通的因为文件描述符的作用域默认情况下只在进程内有效而无法在进程之间进行传递。所以还是需要使用FIFO且注意FIFO是进程与进程间的客户端和服务端本质也属于两个进程所以服务端如果要使用FIFO应该在mkfifo函数中对于FIFO的名字修改不要和服务端的FIFO重名 在服务器端创建另一个FIFO并在不断读数据的子进程中不断判断是否从客户端收到了“quit”如果收到了就立刻回复写一个“Bye”也就是为了让客户端能立刻退出只写打开FIFO并阻塞等待....一旦等待到了有进程只读打开FIFO就会往FIFO写入“quit”然后exit。 同时在不断写数据的父进程中不断非阻塞的只读打开fifo并每次都将光标移到最开头一旦从FIFO读取到了“quit”就关闭FIFO关闭客户端的套接字收集子进程退出状态然后退出循环执行fork之后的exit。 程序框图 server_final.c
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include linux/in.h
#include string.h
#include sys/wait.h
#include stdlib.h
#include sys/stat.h
#include errno.h
#include fcntl.hint main(int argc, char **argv)
{int conn_num 0;int flag 0;int sockfd;int conn_sockfd;int ret;int n_read;int n_write;int len sizeof(struct sockaddr_in);char readbuf[128];char msg[128];int fd; //fifochar fifo_readbuf[20] {0};char *fifo_msg quit;pid_t fork_return;pid_t fork_return_1;struct sockaddr_in my_addr;struct sockaddr_in client_addr;memset(my_addr,0,sizeof(struct sockaddr_in));memset(client_addr,0,sizeof(struct sockaddr_in));if(argc ! 3){printf(param error!\n);return 1;}//socketsockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return 1;}else{printf(socket success, sockfd %d\n,sockfd);}//bindmy_addr.sin_family AF_INET;my_addr.sin_port htons(atoi(argv[2]));//host to net (2 bytes)inet_aton(argv[1],my_addr.sin_addr); //char* format - net formatret bind(sockfd, (struct sockaddr *)my_addr, len);if(ret -1){perror(bind);return 1;}else{printf(bind success\n);}//listenret listen(sockfd,10);if(ret -1){perror(listen);return 1;}else{printf(listening...\n);}//fifoif(mkfifo(./fifo1,S_IRWXU) -1 errno ! EEXIST){perror(fifo);}while(1){//acceptconn_sockfd accept(sockfd,(struct sockaddr *)client_addr,len);if(conn_sockfd -1){perror(accept);return 1;}else{conn_num;if(conn_num 1){printf(there are more then one client, msg may not be sent accuratly!\n);}printf(accept success, no.%d client IP %s\n,conn_num,inet_ntoa(client_addr.sin_addr));}fork_return fork();if(fork_return 0){//father keeps waiting for new request//wait(NULL); //cant wait,will block }else if(fork_return 0){perror(fork);return 1;}else{//son deals with requestfork_return_1 fork();if(fork_return_1 0){//father keeps writing msgwhile(1){fd open(./fifo1,O_RDONLY|O_NONBLOCK);lseek(fd, 0, SEEK_SET);read(fd,fifo_readbuf,20);//printf(read from fifo:%s\n,fifo_readbuf);if(fifo_readbuf[0]q fifo_readbuf[1]u fifo_readbuf[2]i fifo_readbuf[3]t){printf(sorry,the last msg sent fail,client has quit\n);close(fd);close(conn_sockfd);wait(NULL);break;}//writememset(msg,0,sizeof(msg));//printf(\ntype msg:);scanf(%s,(char *)msg);n_write write(conn_sockfd,msg,strlen(msg));if(n_write -1){perror(write);return 1;}else{printf(%d bytes msg sent\n,n_write);}}}else if(fork_return_1 0){perror(fork);return 1;}else{//son keeps reading msgwhile(1){//readmemset(readbuf,0,sizeof(readbuf));n_read read(conn_sockfd,readbuf,128);if(readbuf[0]q readbuf[1]u readbuf[2]i readbuf[3]t){printf(client quit\n);conn_num--;printf(%d client remain\n,conn_num);write(conn_sockfd,BYE,3);fd open(./fifo1,O_WRONLY);write(fd,fifo_msg,strlen(fifo_msg));exit(1);}if(n_read -1){perror(read);return 1;}else{printf(\nclient: %s\n,readbuf);}}}exit(2);}}return 0;
} client_final.c
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include linux/in.h
#include string.h
#include sys/wait.h
#include stdlib.h
#include sys/stat.h
#include errno.h
#include fcntl.hint main(int argc, char **argv)
{int sockfd;int ret;int n_read;int n_write;char readbuf[128];char msg[128];int fd; //fifochar fifo_readbuf[20] {0};char *fifo_msg quit;pid_t fork_return;if(argc ! 3){printf(param error!\n);return 1;}struct sockaddr_in server_addr;memset(server_addr,0,sizeof(struct sockaddr_in));//socketsockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return 1;}else{printf(socket success, sockfd %d\n,sockfd);}//connectserver_addr.sin_family AF_INET;server_addr.sin_port htons(atoi(argv[2]));//host to net (2 bytes)inet_aton(argv[1],server_addr.sin_addr); ret connect(sockfd, (struct sockaddr *)server_addr, sizeof(struct sockaddr_in));if(ret -1){perror(connect);return 1;}else{printf(connect success!\n);}//fifoif(mkfifo(./fifo,S_IRWXU) -1 errno ! EEXIST){perror(fifo);}//forkfork_return fork();if(fork_return 0){//father keeps writing msgwhile(1){//writememset(msg,0,sizeof(msg));//printf(\ntype msg:);scanf(%s,(char *)msg);n_write write(sockfd,msg,strlen(msg));if(msg[0]q msg[1]u msg[2]i msg[3]t){printf(quit detected!\n);fd open(./fifo,O_WRONLY);write(fd,fifo_msg,strlen(fifo_msg));close(fd);close(sockfd);wait(NULL);break;}if(n_write -1){perror(write);return 1;}else{printf(%d bytes msg sent\n,n_write);}}}else if(fork_return 0){perror(fork);return 1;}else{//son keeps reading while(1){fd open(./fifo,O_RDONLY|O_NONBLOCK);lseek(fd, 0, SEEK_SET);read(fd,fifo_readbuf,20);//printf(read from fifo:%s\n,fifo_readbuf);if(fifo_readbuf[0]q fifo_readbuf[1]u fifo_readbuf[2]i fifo_readbuf[3]t){exit(1);}//readmemset(readbuf,0,sizeof(readbuf));n_read read(sockfd,readbuf,128);if(n_read -1){perror(read);return 1;}else{printf(\nserver: %s\n,readbuf);}}}return 0;
} 实现效果
编译并运行server端 编译并运行client端 此时回看server端 此时就可以实现双方聊天的功能了服务器左客户端右 为什么不打成hows it going而是hows/it/going原因是scanf函数如果占位符是“%s”即字符串时空格和换行键都会被视为结束符号 详见scanf与空格_scanf读空格_CSU迦叶的博客-CSDN博客 所以如果输入hows it going的话 解决方法是使用fgets函数 详见如何读取带空格的字符串 直到客户端打出“quit” : 客户端会立刻退出服务器此时只有读端知道客户端退出了 必须再打一句话让服务器写端刷新这样才能从FIFO读取到信息让写端也知道客户端退出了 此时服务器的读写端也全部退出再次进入阻塞状态等待新连接...
下面模拟如果同时有两个或以上连接的时候左侧一个服务器右侧两个客户端 我代码执行的顺序是连接第一个客户端右上-- 服务器发送“hi” -- 连接第二个客户端右下-- 服务器发送“hihi” -- 服务器发送“hihihi” -- 服务器发送“hihihihi”
可见连接第一个客户端的时候服务器发送hi准确的到了右上的客户端1但是当连接第二个客户端了之后服务器分别发送了“hihi”“hihihi” “hihihihi” 从服务端看没有任何区别但实际情况下“hihi”和“hihihihi”到了客户端1“hihihi”到了客户端2明显出现了混乱所以我在代码中设置了提醒检测到大于1个客户端接入时会提醒。 问题探讨 和 一些思路过程
如刚刚所说服务器用了两次fork相当于有3个独立的进程所以我的conn_num变量的设置实际上是相当残疾的因为我把conn_num-- 放在了一个子进程里而fork之后的变量空间都是独立的所以我的conn_num变量只要有客户端推出就不准了应该也使用进程间通信来通知但是鉴于我的代码实现目的本来就是双人聊天所以多客户端的连接部分就没有深入修改了在我目前的代码逻辑中我本来觉得只设置了一个conn_sockfd变量来存放客户端的套接字不合理因为当多个客户端接入的时候套接字可能会被覆盖导致读写异常但是其实一个就够了原因也是因为fork之后变量空间也会被复制根据我的代码逻辑每出现一个新连接就会fork一个子进程来处理这个连接的读写如果有多个客户端就会有多个子进程里面的套接字变量名都是conn_sockfd但其实值是不一样的。并且服务端的第一个fork父进程不能调用wait因为wait会阻塞直到子进程退出但我希望父进程不被阻塞而一直while循环等待新连接所以第一次fork生成的子进程在客户端退出之后会变成僵尸进程且每有一个新的客户端退出就会多一个僵尸进程在当前的逻辑下不可避免。
且当多个客户端连接服务器的时候服务器会针对每个客户端fork一个子进程来处理而每一个子进程都会再fork一个读端不停的scanf检测输入。
但是进程与进程之间是竞争的关系所以在cmd中看来光标一直在闪没有变化但实际上可能上一秒这是客户端1对应子进程的scanf下一秒就变成了客户端2对应子进程的scanf。
这就导致了如果此时对着光标输入消息并回车无法确定收到消息的是客户端1还是客户端2。
解决办法1使用线程实现真正的多方数据收发但是难度很大需要更多的新知识解决办法2舍弃服务器的scanf功能改为自动回复这样可以实现多个客户端对服务器的自定义消息发送但是服务器只能回复预设的内容解决办法3也就是现在实现的效果舍弃多方发送只用一个客户端这样就可以实现客户端和服务器的自定义消息交互但是此时不能增加更多的客户端