【UNIX网络编程(三)】TCP客户/服务器程序示例

上一节给出了TCP网络编程的函数,这一节使用那些基本函数编写一个完成的TCP客户/服务器程序示例。

该例子执行的步骤如下:

1、客户从标准输入读入一行文本,并写给服务器。

2、服务器从网络输入读入这行文本,并回射给客户。

3、客户从网络输入读入这行回射文本,并显示在标准输出上。

用图描述如下:

编写TCP回射服务器程序如下:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SERV_PORT	9877
#define MAXLINE		2048

void
str_echo(int sockfd)
{
	ssize_t	n;
	char	buf[MAXLINE];

again:
	while((n = read(sockfd, buf, MAXLINE)) > 0)
		write(sockfd, buf, n);

	if(n < 0 && errno == EINTR)
		goto again;
	else if(n < 0)
		printf("str_echo : read error");
}

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, IPPROTO_TCP);
	if(listenfd == -1){
		printf("socket fail.\n");
		return -1;
	}

	bzero(&servaddr, sizeof(struct sockaddr_in));
	servaddr.sin_family 	 = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port 		 = htons(SERV_PORT);

	if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))){
		printf("bind fail.\n");
		return -1;
	}
	if(listen(listenfd, 5)){
		printf("listen fail.\n");
		return -1;
	}

	printf("listen stat.\n");

	for(;;){
		clilen = sizeof(cliaddr);
		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
		if(connfd == -1){
			printf("accept fail.\n");
			return -1;
		}
		printf("accept stat.\n");
		if((childpid = fork()) == 0){
			close(listenfd);
			printf("servers.\n");
			str_echo(connfd);
			exit(0);
		}
		close(connfd);
	}

	return 0;
}

TCP回射客户程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#define MAXLINE 2048
#define SERV_PORT 9877

void str_cli(FILE *, int);

int
main(int argc, char **argv)
{
	int 	sockfd;
	struct  sockaddr_in cliaddr;

	if(argc != 2){
		printf("usage:tcpcli <IPaddress>");
		return -1;
	}

	sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sockfd == -1){
		printf("socket fail.\n");
		return -1;
	}

	bzero(&cliaddr, sizeof(cliaddr));
	cliaddr.sin_family = AF_INET;
	cliaddr.sin_port   = htons(SERV_PORT);
	if(!(inet_pton(AF_INET, argv[1], &cliaddr.sin_addr))){
		printf("inet pton fail.\n");
		return -1;
	}

	if(connect(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr))){
		printf("connect fail.\n");
		return -1;
	}

	str_cli(stdin, sockfd);

	exit(0);

}

void
str_cli(FILE *fp, int sockfd)
{
	char 	sendline[MAXLINE], recvline[MAXLINE];

	while(fgets(sendline, MAXLINE, fp) != NULL){
		write(sockfd, sendline, strlen(sendline));

		if(read(sockfd, recvline, MAXLINE) == 0)
			printf("str_cli:server terminated prematurely");

		fputs(recvline, stdout);
	}
}

编译两个程序:

gcc tcpserv.c -o tcpserv

gcc tcpcli.c -o tcpcli

测试,正常启动服务器./tcpserv &,之后启动客户端./tcpcli 127.0.0.1,之后就可以在终端输入一行文本,接着就会显示出该文本。

分析整个建立过程:

1、首先在后台启动服务器,服务器启动后,它调用socket、bind、listen和accept,并阻塞于accept调用。在启动客户程序之前,用netstat程序检查服务器监听套接字的状态。

netstat -a | grep 9877,显示如下内容:

tcp        0      0 *:9877                  *:*                     LISTEN

该行表明,有一个套接字处于LISTEN状态,它有通配的本地IP地址,本地端口为9877。

2、接着在同一主机上启动客户,并指定服务器主机的IP地址为127.0.0.1(环回地址)。./tcpcli 127.0.0.1

客户调用socket和connect,connect引起TCP的三路握手过程。当三路握手完成之后,客户中的connect和服务器中的accept均返回,连接于是建立。

3、客户调用str_cli函数,该函数阻塞与fgets调用。

4、当服务器中的accept返回时,服务器调用fork,再又子进程调用str_echo。该函数调用read,而read在等待客户送入一行文本期间阻塞。

5、另一方面,服务器父进程再次调用accept并阻塞,等待下一个客户连接。

至此,有3个都在睡眠的进程:客户进程、服务器父进程和服务器子进程。

分析终止过程:

连接建立后,在客户的标准输入中键入什么,都会回射到它的标准输出中,在客户正常终止时,客户和服务器的步骤如下:

1、当我们键入EOF字符时,fgets返回一个空指针,于是str_cli函数返回。

2、当str_cli返回到客户的main函数时,main通过调用exit终止。

3、进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,这就是TCP链接终止序列的前半部分。至此,服务器套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2状态。

4、当服务器TCP接收FIN时,服务器子进程阻塞于read调用,于是read返回0。这导致str_echo函数返回服务器子进程的main函数。

5、服务器子进程通过调用exit来终止。

6、服务器子进程中打开的所有描述符随之关闭。

7、进程终止处理的另一部分是:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。

思考:

1、子进程给父进程发送了一个SIGCHLD信号,但父进程没有捕获它,而是采用了默认行为,这样会导致子进程进入僵死状态。

2、上面的都是正常启动正常终止的情况,若服务器进程在客户之前终止,则客户会发生什么?若服务器主机崩溃又会怎样?等等

【UNIX网络编程(三)】TCP客户/服务器程序示例,布布扣,bubuko.com

时间: 2024-10-21 02:48:46

【UNIX网络编程(三)】TCP客户/服务器程序示例的相关文章

【UNIX网络编程】TCP客户/服务器程序示例

做一个简单的回射服务器: 客户从标准输入读入一行文本,写给服务器 -> 服务器从网络输入读入这行文本,并回射给客户 -> 客户从网络输入读入这行回射文本,并显示在标准输出上 以下是我的代码(部分.h文件是由unpv13e文件夹中的.c文件改名得到) #include "../unpv13e/unp.h" #include "../unpv13e/apueerror.h" #include "../unpv13e/wrapsock.h"

UNIX网络编程入门——TCP客户/服务器程序详解

前言 最近刚开始看APUE和UNP来学习socket套接字编程,因为网络这方面我还没接触过,要等到下学期才上计算机网络这门课,所以我就找了本教材啃了一两天,也算是入了个门. 至于APUE和UNP这两本书,书是好书,网上也说这书是给进入unix网络编程领域初学者的圣经,这个不可置否,但这个初学者,我认为指的是接受过完整计算机本科教育的研究生初学者,需要具有完整计算机系统,体系结构,网络基础知识.基础没打好就上来啃书反而会适得其反,不过对于我来说也没什么关系,因为基础课也都上得差不多了,而且如果书读

unix网络编程各种TCP客户-服务器程序设计实例(二)

本节我们接着介绍另外的几种TCP客户-服务器程序: 第四种:TCP并发服务器,每个客户一个子线程 在我们前面的并发服务器程序例子中可以看出:父进程接受连接,派生子进程,子进程处理与客户的交互. 这种模式的问题: fork()是昂贵的.内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等. fork()子进程后,需要用进程间通信在父子进程之间传递信息. 一个进程中的所有线程共享相同的全局内存,这使得线程很容易共享信息,但是这种简易型也带来了同步问题.一个进程中的所有线程不仅共享全局变量,

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

一,到http://download.csdn.net/detail/ts173383201/4505201去下载源代码,然后解压: 二,cd到你解压后的文件夹下,就是有configure的那个目录下,执行命令./configure: 三,执行cd lib跳到lib目录下,执行make命令,会在上层目录(就是刚才有configure那个目录)生成libunp.a文件 四,复制这个静态库libunp.a到/usr/lib/和/usr/lib64/中; 五,接下来在目录中找到unp.h和config

unix网络编程各种TCP客户-服务器程序设计实例(三)

第五种  TCP预先派生子进程服务器程序: 对预先派生子进程服务器的最后一种改动就是由父进程调用accept,然后再将所接受的已连接描述字传递给子进程.父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的描述字.为每个子进程维护一个信息结构,用来管理各子进程. 在调用fork之前,先创建一个字节流管道(Unix域的字节流套接口),它是Unix域的字节流套接口.当子进程派生后,父进程关闭一个描述字(sockfd[1]),子进程关闭另一个描述字(sockfd[0]),此外,子进程将流管道的字节所

UNIX网络编程笔记(4)—TCP客户/服务器程序示例

TCP客户/服务器程序示例 这一章信息量开始大起来了,粗略来看它实现了简单的TCP客户/服务器程序,里面也有一些费解的细节. 1.概述 完整的TCP客户/服务器程序示例.这个简单的例子将执行如下步骤的一个回射服务器(这里的回射服务器就是服务简单的把客户端发送的消息返回给客户): 1)客户从标准输入读入一行文本,并写给服务器 2)服务器从网络输入读入这行文本,并回射给客户 3)客户从网络输入读入这行回射文本,并显示在标准输出上 这样实际上就构成了一个全双工的TCP连接. 本章就围绕了这个简单的TC

TCp客户/服务器程序示例

1. TCP回射服务器程序:main函数 #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, sizof(servaddr

《UNIX网络编程》TCP客户端服务器:并发、消息回显

经过小小改动,把前面基础的例子做出一点修改. 并发服务器,服务器每accept一个请求就fork()一个新的子进程. 编译运行方法同前一篇. /*client_tcp.c*/ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #incl

Java网络编程(tcp在服务器上应用多线程)

package org.tcp; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class EchoThread implements Runnable { private Socket client = null; public EchoThread(Socket client){ this.c