⟅UNIX网络编程⟆⦔TCP客户/服务器程序实例(一)

说在前面

功能介绍

  1. 客户从标准输入(例如键盘)读入一行文本,并写给服务器
  2. 服务器从网络输入读入这行文本,并回射(转发)给客户
  3. 客户从网络输入读入这行回射文本,并显示于标准输出(例如屏幕)上
    在这里插入图片描述
    客户端与服务器之间是全双工连接(即可同时发送和接收);writenreadline⟅UNIX网络编程⟆⦔readn、writen、readline函数

服务器程序

#include	"unp.h"
#define SERV_PORT 9877
void 
str_echo(int sockfd)
{
	// 注:该函数采用UNIX网络编程第三版中文版中的版本
	// 同时,将goto语句去掉了
	ssize_t		n;
	char		buf[MAXLINE];

	for( ; ; )
	{
		while( (n = read(sockfd, buf, MAXLINE)) > 0)
			Writen(sockfd, buf, n);

		if(n < 0 && errno == EINTR)
			continue;
		else if(n < 0)
			err_sys("str_echo: read error");
		else
			break;
	}
}

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); 	/* 绑定本地端口,9877 */

	Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); /* bind调用 */

	Listen(listenfd, LISTENQ); 						/* 转换为监听套接字 */

	for ( ; ; ) {
		clilen = sizeof(cliaddr); 					/* 地址结构长度 */
		connfd = Accept(listenfd, (struct sockaddr *) &cliaddr, &clilen); /* 返回连接套接字 */

		if ( (childpid = Fork()) == 0) {			/* fork子进程 */
			Close(listenfd);						/* 关闭监听套接字 */
			str_echo(connfd);						/* 处理请求 */
			exit(0);
		}
		Close(connfd);								/* 父进程关闭连接套接字 */
	}
}
  • main函数中各种细节见目录中基础部分
    主进程(父进程)在accept接受一个连接后,调用fork函数产生一个子进程,子进程继续处理该连接来自客户的请求。
  • 关于str_echo函数
    str_echo函数只是将read到的数据发送(write)回客户。
    在该函数的read调用时,默认单次read不保证读取完所有客户的数据,因此使用了while循环。
  • Writen
    Writen为函数writen的一个包裹函数。上述代码中,所有首字母大写的均为包裹函数。具体实现见代码。

客户端程序

#include	"unp.h"
#define SERV_PORT 9877

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);
    }
}

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, (struct sockaddr *) &servaddr, sizeof(servaddr)); /* connect */

	str_cli(stdin, sockfd);								/* 客户端处理工作 */

	exit(0);
}
  • main函数中各种细节见目录中基础部分
    客户端是一些基本流程,主要工作交由str_cli函数处理。
  • str_cli函数
    在主函数中,str_cli的实参是stdin,表示标准输入,在头文件“stdio”中定义。
    在str_cli函数中,由fgets函数标准输入读取一行
    Fgets(sendline, MAXLINE, fp) //Fgets为fgets的包裹函数
    
    然后,将读取到的数据使用Writen函数发送给服务器;
    Writen(sockfd, sendline, strlen(sendline));
    
    之后,使用Readline函数接收服务器的回射数据,并存储到recvline中;
    Readline(sockfd, recvline, MAXLINE)
    
    最后,使用fputs函数将recvline中的数据发送给标准输出
    Fputs(recvline, stdout); // Fputs为fputs的包裹函数
    
  • Writen
    Writen为函数writen的一个包裹函数。
  • Readline
    Readline为函数readline的一个包裹函数。上述代码中,所有首字母大写的均为包裹函数。具体实现见代码。

代码

编译

  • 使用cmake。
  • 文件结构
    在这里插入图片描述
  • 编译
    cmake .
    make
    
    在这里插入图片描述在这里插入图片描述

运行

发布了106 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_33446100/article/details/103731093