第五章:TCP客户-服务器程序例子

TCP客户-服务器程序例子

首先编写一个客户端在标准输入上读取内一行内容,然后发送给服务器,之后服务器直接返回数据报的内容,此时客户端收到信息,并且把数据报写到标准输出设备上,如下图


此章节就是根据这个小例子,来让你知道某些api底层的东西,使你对这些api的了解更加深刻

贴代码

服务器端的小程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <strings.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define SERV_PORT 9877
#define WAIT_COUNT 5
#define READ_SIZE 257

void send_myself(int fd);
void sig_child(int signo)
{
	int stat;
	pid_t pid = wait(&stat);
	printf("pid is %d\n",pid);
	return;
}
int main(int argc, char** argv)
{
	int listen_fd, real_fd;
	struct sockaddr_in listen_addr, client_addr;
	socklen_t len = sizeof(struct sockaddr_in);
	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_fd == -1)
	{
		perror("socket failed   ");
		return -1;
	}
signal(SIGCHLD, sig_child);
	bzero(&listen_addr,sizeof(listen_addr));
	listen_addr.sin_family = AF_INET;
	listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	listen_addr.sin_port = htons(SERV_PORT);
	bind(listen_fd,(struct sockaddr *)&listen_addr, len);
	listen(listen_fd, WAIT_COUNT);
    while(1)
	{
		real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
		if(real_fd == -1)
		{
			perror("accpet fail  ");
			return -1;
		}
		if(fork() == 0)
		{
			close(listen_fd);
			send_myself(real_fd);
			close(real_fd);
			exit(0);			
		}
		close(real_fd);
	}	
	return 0;
}
void send_myself(int fd)
{
	char tmp[READ_SIZE] = {0};
	while(1)
	{
		int size = read(fd ,tmp,READ_SIZE-1);
		write(STDOUT_FILENO ,tmp,size);
		if(size == 0)
		{
			perror("read fail   ");
			return ;
		}
		if(write(fd,tmp,size) == 0)
		{
			perror("write client failed ");
			return ;
		}
		bzero(tmp,READ_SIZE);
	}
}

客户端的小程序:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define SER_IP "127.0.0.1"
#define SER_PORT 9877
#define MAX_LINE 128
void send_myself(int fd)
{
	char tmp[MAX_LINE] = {0};
	int size;
	fgets(tmp,MAX_LINE - 1,stdin);
	write(fd,tmp, strlen(tmp));
	bzero(tmp,MAX_LINE);
	//write(STDOUT_FILENO, tmp, MAX_LINE);
	//write(STDOUT_FILENO,"\n",strlen("\n"));
	if((size = read(fd,tmp,MAX_LINE -1)) == 0)
	{
		perror("read serv fail");
		return ;
	}
	//tmp[size] = '\n';
	//fputs(tmp,stdout);
	//write(STDOUT_FILENO, tmp, strlen(tmp));
	printf("size is %d\n",size);
	printf("%s",tmp);
	fflush(stdout);
	return ;
	
}
int main(int argc, char** argv)
{
	int send_sk;
	struct sockaddr_in s_addr;
	socklen_t len = sizeof(s_addr);
	send_sk = socket(AF_INET, SOCK_STREAM, 0);
	if(send_sk == -1)
	{
		perror("socket failed  ");
		return -1;
	}
	bzero(&s_addr, sizeof(s_addr));
	s_addr.sin_family = AF_INET;
	//s_addr.sin_addr.s_addr = htonl(
	inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);
	s_addr.sin_port = htons(SER_PORT);
	if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)
	{
		perror("connect fail  ");
		return -1;
	}
	while(1)
	{
		send_myself(send_sk);	
	}
}


当服务器启动的时候进入监听状态,服务器刚好执行完socket、bind、listen、阻塞在accept上等待客户端的连接(属于被动连接)


当客户端在本地启动的时候,与服务器进行了连接,服务器accept返回,这个时候,就建立完连接,同时呢,服务器,父进程再次阻塞在accept这个函数调用上,这个时候,等在客户端的再一次连接


这个时候查看一下进程就会知道,服务器的子进程和父进程,如下图


当我们结束客户端的时候,立即查看服务和客户端的状态,如下图,如图所示,这个客户端进入了TIME_WAIT状态


由于没有对SIGCHLD信号进行处理是子进程变成僵尸进程了,如下图


僵尸进程状态是z(zombie)“+”代表的是前台进程,加入信号处理程序之后,就不会是子进程处于僵尸状态(哎!太可怕了)

当把与客户端连接的子进程kill掉的时候,客户端进入到CLOSE_WAIT状态,这个时候,那个子进程还没有收到客户端的ack信息,此时处于FIN_WAIT状态


过了一段时候后,子进程收到客户端的ack信息,半关闭就完成了,子进程就结束了如下图:


此时,如果客户端再次想那个子进程发送数据的时候,因为此时这个进程已经结束,在这个端口和ip上没有了进程,导致服务器会发送一个RST数据报,结束客户端程序

 

如果客户端已经接受到一个RST数据报的时候,这个时候在向服务器端发送数据的时候,内核会向客户端进程发送一个SIGPIPE信号,是客户端进程结束。
服务器主机崩溃的状态

如果,客户端和服务器已经建立了连接的时候,此时服务器崩溃(达到这一标准可以把服务器的网线拔掉,这个时候,服务器就不能发送FIN数据报了,和关机不一样的)

此时如果客户端向服务器发送数据的时候,因为服务器已经不存在了,那么客户端就不能接受到服务器给客户端的ack信息,这个时候,客户端建立的是TCP连接,就会重发数据报,发送多少次之后就会返回超时,前面所说的ETIMEOUT

服务器主机崩溃后重启:

当客户端和服务器已经建立连接的时候,服务器发生崩溃,重新启动的时候,丢失了原来和客户端的连接信息,这个时候,当客户端向服务器发送数据的时候(客户端并不知道,服务器已经忘记三次握手了),此时服务器发送RST数据报,就结束了客户端的发送

服务器关机:

会断开tcp连接,会发送FIN数据报



没有更多推荐了,返回首页