您的位置:首页 > 理论基础 > 计算机网络

网络编程培训之三 实现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增加可靠性。

如果觉得文章对你有帮助,可以通过支付宝打赏点:

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: