unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法(一)

本文介绍UNIX网络编程中的九种TCP客户程序设计实例,包括迭代服务器、并发服务器及使用select/poll的服务器程序等,深入剖析代码实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:在以前的UNIX网络编程系列中我们介绍了有关网络编程的理论知识,本文将在其基础上总结《UNIX网络编程》上的9种不同的TCP客户程序设计例子,希望能给大家带来帮助。本文只介绍实例,相关的理论知识请参考《unix网络编程》或者本博文的http://blog.youkuaiyun.com/ts173383201/article/category/1213821系列;

在出代码之前,可能有很多同学遇到《UNIX网络编程》上的例子编译的问题,下面就先介绍网络编程上例子编译环境的搭建方法,这里先说明我们系统是ubuntu:

一,到http://download.youkuaiyun.com/detail/ts173383201/4505201去下载源代码,然后解压;

二,cd到你解压后的文件夹下,就是有configure的那个目录下,执行命令./configure;

三,执行cd lib跳到lib目录下,执行make命令,会在上层目录(就是刚才有configure那个目录)生成libunp.a文件

四,复制这个静态库libunp.a到/usr/lib/和/usr/lib64/中;

五,接下来在目录中找到unp.h和config.h,在以后的代码中我们都要用到这两个头文件,将他们复制到和我们的源代码同一个路径下;

这样我们的环境就搭建好了,是不是很简单啊,但是以后编译的时候在gcc的最后加上-lunp导入静态库就可以了。下面我们就来看这些例子:

第一种:TCP迭代服务器程序:迭代服务器总是在完全处理了一个客户的请求后才响应下一个客户的请求。

客户端程序:daytimetcpcli.c

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd, n;
	char				recvline[MAXLINE + 1];
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: a.out <IPaddress>");

	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_sys("socket error");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port   = htons(13);	/* daytime server */
	if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
		err_quit("inet_pton error for %s", argv[1]);

	if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
		err_sys("connect error");

	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
		recvline[n] = 0;	/* null terminate */
		if (fputs(recvline, stdout) == EOF)
			err_sys("fputs error");
	}
	if (n < 0)
		err_sys("read error");

	exit(0);
}

服务器程序:daytimetcpsrv.c

#include	"unp.h"
#include	<time.h>

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	struct sockaddr_in	servaddr;
	char				buff[MAXLINE];
	time_t				ticks;

	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(13);	/* daytime server */

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	for ( ; ; ) {
		connfd = Accept(listenfd, (SA *) NULL, NULL);

        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        Write(connfd, buff, strlen(buff));

		Close(connfd);
	}
}

好,现在我们接着上面的步骤,将这两个源文件放到我们喜欢的目录下,再复制unp.h和config.h两个文件到同一个目录下;

执行下面的命令:

生成了server和client程序,运行server和client程序如下图,顺利成功:
 

第二种:TCP并发服务器程序,每个客户一个子进程

每个客户一个子进程:传统上,并发服务器调用fork派生一个子进程来处理每个客户。这使得服务器可在同一时间为多个客户提供服务。

回射服务器:

1, 客户从标准输入读一行文本,写到服务器上;

2, 服务器从网络输入读此行,并回射给客户;

3, 客户度此回射行并写到标准输出;

#include	"unp.h"

void
sig_chld(int signo)
{
	pid_t	pid;
	int		stat;

	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;
	void				sig_chld(int);

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

	Signal(SIGCHLD, sig_chld);	/* must call waitpid() */

	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
			if (errno == EINTR)
				continue;		/* back to for() */
			else
				err_sys("accept error");
		}

		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 */
	}
}
str_echo.c:

#include "unp.h"

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

注意:上面的Signal(SIGCHLD, sig_chld);是为了捕捉信号SIGCHLD并处理僵尸进程,有关僵尸进程的介绍请大家参看博文unix网络编程之基本套接口编程while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0),这里我们不能用wait,由于wait会阻塞于没有子进程终止的情况所有不能用while循环每个终止信号。

客户端程序:

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

str_cli.c:

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

最后像上面一样编译后运行即可,效果:

 
第三种:使用单进程和select/poll的TCP服务器程序

这种方法是用select来处理任意数目的客户的单进程程序,而不是像上面为每个客户派生一个子进程,在给出具体代码之前,让我们介绍一下背景知识,以及对用以跟踪客户的数据结构进行仔细的分析;

如果一个或多个I/O条件满足(例如,输入已准备好被读,或者描述字可以承接更多的输出)时,我们就被通知到。这个能力被称为I/O复用,是由函数selectpoll支持的。

 

先介绍一下各种I/O模型:

阻塞I/O,非阻塞I/O,I/O复用,信号驱动I/O(SIGIO),异步I/O

对于一个套接口上的输入操作,第一步一般是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用缓冲区。

select函数:

这个函数运行进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒。

作为一个例子,我们可以调用函数select并通知内核仅在下列情况发生时才返回:

集合{14,5}中的任何描述字准备好读,或

集合{2,7}中任何描述字准备好些,或

集合{1,4}中的任何描述字有异常条件待处理,或

已经过了10.2

也就是说通知内核我们对哪些描述字感兴趣以及等待多长时间我们所关心的描述字不受限于套接口任何描述字都可用select来测试.

数据结构图示:

 其中client[]用来揭露已连接描述字,而reset为读描述字集。

服务器程序:

/* include fig01 */
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					i, maxi, maxfd, listenfd, connfd, sockfd;
	int					nready, client[FD_SETSIZE];
	ssize_t				n;
	fd_set				rset, allset;
	char				buf[MAXLINE];
	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);

	maxfd = listenfd;			/* initialize */
	maxi = -1;					/* index into client[] array */
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* -1 indicates available entry */
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
/* end fig01 */

/* include fig02 */
	for ( ; ; ) {
		rset = allset;		/* structure assignment */
		nready = Select(maxfd+1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(listenfd, &rset)) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s, port %d\n",
					Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
					ntohs(cliaddr.sin_port));
#endif

			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd;	/* save descriptor */
					break;
				}
			if (i == FD_SETSIZE)
				err_quit("too many clients");

			FD_SET(connfd, &allset);	/* add new descriptor to set */
			if (connfd > maxfd)
				maxfd = connfd;			/* for select */
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

			if (--nready <= 0)
				continue;				/* no more readable descriptors */
		}

		for (i = 0; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
						/*4connection closed by client */
					Close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else
					Writen(sockfd, buf, n);

				if (--nready <= 0)
					break;				/* no more readable descriptors */
			}
		}
	}
}
/* end fig02 */

使用poll的服务器程序:

/* include fig01 */
#include	"unp.h"
#include	<limits.h>		/* for OPEN_MAX */

int
main(int argc, char **argv)
{
	int					i, maxi, listenfd, connfd, sockfd;
	int					nready;
	ssize_t				n;
	char				buf[MAXLINE];
	socklen_t			clilen;
	struct pollfd		client[OPEN_MAX];
	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);

	client[0].fd = listenfd;
	client[0].events = POLLRDNORM;
	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1;		/* -1 indicates available entry */
	maxi = 0;					/* max index into client[] array */
/* end fig01 */

/* include fig02 */
	for ( ; ; ) {
		nready = Poll(client, maxi+1, INFTIM);

		if (client[0].revents & POLLRDNORM) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif

			for (i = 1; i < OPEN_MAX; i++)
				if (client[i].fd < 0) {
					client[i].fd = connfd;	/* save descriptor */
					break;
				}
			if (i == OPEN_MAX)
				err_quit("too many clients");

			client[i].events = POLLRDNORM;
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

			if (--nready <= 0)
				continue;				/* no more readable descriptors */
		}

		for (i = 1; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & (POLLRDNORM | POLLERR)) {
				if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) {
							/*4connection reset by client */
#ifdef	NOTDEF
						printf("client[%d] aborted connection\n", i);
#endif
						Close(sockfd);
						client[i].fd = -1;
					} else
						err_sys("read error");
				} else if (n == 0) {
						/*4connection closed by client */
#ifdef	NOTDEF
					printf("client[%d] closed connection\n", i);
#endif
					Close(sockfd);
					client[i].fd = -1;
				} else
					Writen(sockfd, buf, n);

				if (--nready <= 0)
					break;				/* no more readable descriptors */
			}
		}
	}
}
/* end fig02 */

 

客服端程序:

#include	"unp.h"

void
str_cli1(FILE *fp, int sockfd)
{
	int			maxfdp1, stdineof;
	fd_set		rset;
	char		buf[MAXLINE];
	int		n;

	stdineof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */
			if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
				if (stdineof == 1)
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");
			}

			Write(fileno(stdout), buf, n);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
				stdineof = 1;
				Shutdown(sockfd, SHUT_WR);	/* send FIN */
				FD_CLR(fileno(fp), &rset);
				continue;
			}

			Writen(sockfd, buf, n);
		}
	}
}

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_cli1(stdin, sockfd);		/* do it all */

	exit(0);
}

这里客服端程序用了shutdown关闭套接口来代替close;下面就来介绍一下两者的区别:

shutdown可以分别关闭读写或者同时关闭读写

如果关闭读,则接受缓冲区的未读出的所有数据都将丢失,以后不会再接受任何数据

如果关闭写,如果输出缓冲区内有数据,则所有的数据将发送出去后将发送一个FIN信号

而close则是关闭该socket,马上发送FIN信号,所有的未完成发送或者接受的数据都将被丢失

对于慢速网络,应该先进行shutdown,然后一定的时间延迟,再close该socket.

(在你已经发送成功后,而对方如果没有接受完毕,你此时如果关闭socket则对方将收到FIN信号,将不能在接收数据,因此容易出现数据丢失的问题,这是 我以前写聊天室时遇到的一个错误,客户端经常报一个与服务器的连接被重置,后来加了一个时间延迟就好了)

#include<sys/socket.h>

int shutdown(int sockfd,int how);

how 的方式有三种分别是

SHUT_RD(0):关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。

SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。

SHUT_RDWR(2):关闭sockfd的读写功能。

成 功则返回0,错误返回-1,错误码errno:EBADF表示sockfd不是一个有效描述符;ENOTCONN表示sockfd未连 接;ENOTSOCK表示sockfd是一个文件描述符而不是socket描述符。

close的定义如下:

#include<unistd.h>

int close(int fd);

关闭读写。

成功则返回0,错误返回-1,错误码errno:EBADF表示fd不是一个有效 描述符;EINTR表示close函数被信号中断;EIO表示一个IO错误。

下面摘用网上的一段话来说明二者的区别:

close----- 关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id

shutdown-- 则破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号.

1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。

2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会影响到其它进程

 

拒绝服务性攻击:

当一个服务器正在处理多个客户时,服务器决不能阻塞于只与单个客户相关函数调用。如果这样的话,服务器悬挂并拒绝为所有其他客户提供服务,这称为拒绝服务攻击,当一个服务器正在处理多个客户时,服务器决不能阻塞于只与单个客户相关的函数调用。如果这样的话,服务器将悬挂并拒绝为所有其他客户提供服务,这称为拒绝服务型攻击。可能解决的办法有:使用非阻塞I/O模型;让每个客户由单独的控制线程提供服务;对I/O操作设置超时;

非阻塞I/O

缺省状态下,套接口是阻塞方式的。这意味着当一个套接口调用不能立即完成时,进程进入睡眠状态,等待操作的完成。我们将可能阻塞的套接口调用分成四种。

非阻塞connect:

在一个TCP套接口被设置为非阻塞后调用connect,connect会立即返回一个EINPROCESS错误,但TCP的三路握手继续进行。在这之后我们可以用select检查这个链接是否建立成功。

 

其他方法我们都在后面的例子中介绍,敬请关注。

转载于:https://www.cnblogs.com/javaspring/archive/2012/08/16/2656213.html

1、制作ramdisk,给出主次设备号,并使用df命令显示相关信息。(20分) 要求:(1) ramdisk的大小为学号的后3位*2,单位为M; (2)对文件系统采用ext2进行格式化。 2、编写个c程序实现下面的功能:监视个文件,如果文件被其他进程追加了内容,就把追加的内容打印出来。(15) (1) 假定所编辑的c程序为mytail.c,使用命令行方式将该源程序编译,目标执行程序为mytail。给出具体的编译实现过程。 (2) 假定日志文件为/usr/tmp/pppd.log,给出打印追加内容的方法。 3、设用户mali记录了在Linux系统中的某些用户的月工资清单,记录在文件mylist.txt中,假定文件mylist.txt的内容如下: #================================================== # 登录名 工作证号 姓名 月份 工资 奖金 补助 扣除 总额 #--------------------------------------------------- wang 2076 wangxi 01 1782 1500 300 175 3407 liang 2074 liangyu 02 1560 1400 280 90 3150 zhang 3087 zhangdi 03 1804 1218 206 213 3015 wang 2076 wangxi 03 1832 1550 230 245 3367 wang 2076 wangxi 04 1832 1450 230 245 3267 liang 2074 liangyu 05 1660 1450 230 70 3270 zhang 3087 zhangdi 06 1700 1310 283 270 3023 #================================================== 只允许用户zhang读取行首字符为#的行用户zhang有关的行,其他用户有关的行对用户zhang保密。(15) (1) 编写相应的查询query.c,给出目标文件为query的实现方法(2) 给出设置mylist.txt文件权限为仅对文件主具有读写权限的实现命令; (3) 给出设置query文件用户ID权限实现命令; (4) 给出用户zhang执行程序query的结果。 4、使用fork,exec以及wait函数构造简单的shell解释程序。(15) (1) 假定所编辑的c程序为myxsh.c,使用命令行方式将该源程序编译,目标执行程序为myxsh。给出具体的编译实现过程。 (2) 假定当前目录下有文件信息如下: 文件的行大小 文件名 文件的行大小 文件名 920 auther.c 146 licp.txt 127 myxsh.c 124 fus.h 160 chap1.h 152 myxse.c 46 fsme.h 164 fsme 运行myxsh程序出现提示符=>后,分析find . -name *.[ch] -exec wc -l {} ; 执行结果。 5、对/usr目录进行压缩归档,结果文件名位myusr.tar。(10分) 要求:(1)以控制台方式运行; (2)将该任务挂起,再转后台,再转前台,查看进程的状态变迁过程。 6、编写程序实现通过Windows客户端对Linux服务器端进行相关网络信息配置。(25) 要求:(1)新修改服务器的IP 地址的构成方式:网络号为192.168.2.0,主机号为学号的后3位-150;掩码为255.255.255.0;默认网关为:网络服务器相同,主机号为1,DNS202.117.96.10; (2)服务器端程序以deamon程序的方式运行,监听端口为学号的后4位; (3)客服端使用VC++,以GUI 方式完成对服务器端的IP 地址、掩码、默认网关、DNS信息配置输入; (4)服务器端在接收客户端的配置信息后,使用exec 系列函数完成对IP 地址、掩码、默认网关、DNS修改,并写入相应的配置文件。结果验证:使用ifconfig检查IP地址信息配置的正确性;使用nslookup检查DNS信息的正确性。 把实现的信息结果配置文件比较,检查其致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值