网络编程培训之三 实现TCP/UDP的简单Echo服务器
2018-03-31 22:30
603 查看
系列博客参考:http://blog.csdn.net/zy416548283/article/category/1108400
代码以编号对应放在Github上:https://github.com/zy416548283/networkProgramming
题目
分别采用面向连接的和无连接的方式实现网络上Echo服务器。Echo服务器即,客户端向Server发送一段字符串,Server收到之后返回给客户端同样的字符串。
题目解读
- 熟悉TCP和UDP基本的Socket API;
- 熟悉服务器和客户端的程序流程;
- 熟悉TCP和UDP编程之间的区别;
- 流程中可能会遇到的一些异常情况,如何处理,保证程序的健壮性;
- 并发服务器和迭代服务器之间的区别;
实现
声明:程序取自UNP第5章和第8章,关于UNP的源码获取和编译可以参考本系列的文章。
TCP(面向连接):
Server端:
//tcpserver #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for ( ; ; ) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /* parent closes connected socket */ } }
可以看出,服务器端的流程:创建套接字–初始化一些参数–绑定端口–监听–接受连接–创建子进程来处理新的连接–关闭连接。
其中,处理字符串的函数如下:
void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while ( (n = read(sockfd, buf, MAXLINE)) > 0) Writen(sockfd, buf, n); if (n < 0 && errno == EINTR) goto again; else if (n < 0) err_sys("str_echo: read error"); }
client
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: tcpcli <IPaddress>"); sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); /* do it all */ exit(0); }
可以看到,Client端的运行流程:创建套接字–初始化参数–连接Server–发送和接收字符串–退出程序(关闭套接字)。
其中str_cli如下:
#include "unp.h" void str_cli(FILE *fp, int sockfd) { char sendline[MAXLINE], recvline[MAXLINE]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Writen(sockfd, sendline, strlen(sendline)); if (Readline(sockfd, recvline, MAXLINE) == 0) err_quit("str_cli: server terminated prematurely"); Fputs(recvline, stdout); } }
编译/运行程序
Server端:
gcc tcpserv01.c -o server01 -lunp ./server01
Client端:
gcc tcpcli01.c -o client01 -lunp ./client01 127.0.0.1
注意事项
- 可以使用netstat命令查看套接字的状态,理解正常情况下TCP的状态转换;
- 处理僵死进程,wait和waitpid的使用;
- 服务器进程终止,客户端处于输入状态,怎么从Server接收到终止消息,考虑select的解决方案;
- 服务器主机崩溃;
- 服务器主机崩溃后重启。
UDP
Server
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr, cliaddr; sockfd = Socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); }
可以看到,UDP Server端的流程是:创建套接字–初始化参数–绑定端口–迭代处理请求–退出(关闭套接字)
其中,dg_echo如下:
#include "unp.h" void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen) { int n; socklen_t len; char mesg[MAXLINE]; for ( ; ; ) { len = clilen; n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); Sendto(sockfd, mesg, n, 0, pcliaddr, len); } }
这里和TCP的并发处理不太一样,采用的是迭代处理请求。因为TCP可能会保持长连接,不会发一个请求就关闭,需要占用Server,故采用并发。而这里的UDP处理的是短消息,从缓冲区里FIFO处理消息,采用迭代的方式来处理。具体可以参考博客:https://www.geek-share.com/detail/2645335163.html
client
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = Socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); }
可以看到,udp client端的流程是:创建套接字–初始化参数–发送接收数据–退出程序(关闭套接字)
其中,dg_cli函数如下:
#include "unp.h" void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline = 0; /* null terminate */ Fputs(recvline, stdout); } }
编译和运行
和上面TCP的编译运行方式一致
注意事项
- client先于Server运行,会导致客户阻塞于recvfrom,这里采用connect可以检测到sendto的错误,也可以给recvfrom设置超时等待时间;
- UDP缺乏流量控制,Client端疯狂发数据会淹没接收端;
- 可以考虑,如何给UDP增加可靠性。
如果觉得文章对你有帮助,可以通过支付宝打赏点:
相关文章推荐
- <网络编程培训之三> 实现TCP/UDP的简单Echo服务器
- 网络编程之:TCP服务器的简单实现
- 网络编程培训之一 编程实现IP/TCP/UDP报文
- <网络编程培训之一> 编程实现IP/TCP/UDP报文
- Linux网络编程 使用epoll实现一个高性能TCP Echo服务器
- 【网络】实现简单的TCP、UDP服务器、TCP多进程/多线程服务器
- 简单的IPv6 UDP/TCP socket编程 -- 两台Linux实现简单的ipv6通信
- Python 网络编程---简单的服务器与客户端实现---阻塞式编写
- linux网络编程--服务器客户端(TCP实现)
- linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- Java一步一脚印—UDP网络编程的简单实现
- Linux网络编程:一个简单的正向代理服务器的实现
- Python网络编程 3.1 由简单的TCP服务器、客户端程序分析相关方法
- Linux 网络编程基础---------------客户端/服务器的简单实现
- Java网络编程 - 基于UDP协议 实现简单的聊天室程序
- 网络编程:使用Socket实现简单的服务器和客户端的通信
- linux下C/C++网络编程基本:socket实现tcp和udp的例子
- 一种减轻服务器负担并提高客户端间通讯效率的网络通讯设计[TCP/UDP合用] | 简单加密
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现