练习:TCP多进程通信

1.客户端:
socket()-connect()-read()/write()/send()/recv()-close()

2.服务器:
socket()-bind()-listen()-accept()-read()/write()/send()/recv()-close()

3.socket的填充内容,注意其中一个地址信息结构体:struct sockaddr

我们一般不对这个结构体操作。而是对其他结构体赋值,最后再强制转换为(struct sockadd)类型就可以了。

4.注意对长等待函数(阻塞模式)需要考虑中断信号的影响

5.close(fd)函数对fd的关联计数器-1,只有当计数器=0时才关闭fd。因此子进程继承了父进程打开的fd,但是却用不上时需要关闭继承过来的fd。

6.accept()函数传入的是监听套接字,返回是一个全新的连接套接字。2者的fd不同,也即这2个套接字是不同的!

下面代码以TCP通信为例:

/* 头文件  MYHEAD.h */
#ifndef _MYHEAD_H_
#define _MYHEAD_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <wait.h>
#include <errno.h>
#include <arpa/inet.h>


#endif

#include "MYHEAD.h"
//使用示例:./client 192.168.1.100 5001

int main(char argc,char **argv){
	/* 判断输入参数个数 */	
	if(argc<3){
		printf("Usage:%s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	int fd ;
	/* 创建socket */
	if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
		perror("socket");
		exit(-1);
	}
	uint32_t ip;	//容器
	/* 192.xxx.xxx.xxx字符串形式,转换,32位,网络字节序 */
	if(inet_pton(AF_INET,argv[1],(void *)&ip) != 1){
		perror("inet_pton");
		exit(-1);
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = ip;
	addr.sin_port = htons(atoi(argv[2]));	//网络字节序
	/* 链接connect */
	if(connect(fd,(struct sockaddr *)&addr,sizeof(addr))<0){
		perror("connect");
		exit(-1);
	}
	char buf[30];
	int n;
	while(1){
		if(fgets(buf,30,stdin)<0){		//返回0代表没有,返回-1代表出错
			perror("fgets");
			exit(-1);
		}
		if((n=write(fd,buf,strlen(buf))<0)){	//返回实际写入字节数或EOF
			perror("write");
			exit(-1);
		}
		if(strcmp(buf,"quit\n") == 0){	//细节:字符串末尾\n
			close(fd);
			exit(0);
		}
	}
}
#include "MYHEAD.h"
#define LOCAL_PORT		5001
#define	BACKLOG			5






void child_process(int fd,struct sockaddr_in client);		//子进程
void handler(int signal);									//响应函数

int main(char argc,char **arcv)
{
	int fd,newfd;
	pid_t pid;
	/* 绑定:信号--响应函数。子进程退出时,内核会发送SIGCHLD通知父进程 */
	signal(SIGCHLD,handler);
	/* 创建socket */
	if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
		perror("socket");
		exit(-1);
	}
	/* 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
	
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(LOCAL_PORT);			//字节序转换
	addr.sin_addr.s_addr = htonl(INADDR_ANY);	//(INADDR_ANY)表示本机任意网卡地址
	/* 绑定服务器的ip,port给刚创建的socket */
	if(bind(fd,(struct sockaddr *)&addr,sizeof(addr)) < 0)
	{
		perror("bind");
		exit(-1);
	}
	/* 监听,socket变成被动的 */
	if(listen(fd,BACKLOG) < 0)		//BACKLOG=同时请求的客户端个数
	{
		perror("listen");
		exit(-1);
	}
	socklen_t client_len;			//容器
	struct sockaddr_in client;		//容器,存放客户端的地址信息
	while(1){
		/* 阻塞等待建立链接accept,fd属于监听类socket(被动),newfd属于连接类socket(主动) */
		if((newfd = accept(fd,(struct sockaddr *)&client,&client_len))<0)
		{
			if(errno != EINTR){				//accept属于阻塞函数,要考虑被中断信号打断的情况。
				perror("accept");
				exit(-1);
			}
		}
		printf("newfd:%d,fd:%d\n",newfd,fd);	//每个进程都有一个独立的表,因此每个进程打印出来的fd数值可以相同
		if((pid=fork())<0){
			perror("fork");
			exit(-1);
		}
		if(0 == pid){				//子进程
			close(fd);				//关闭监听socket,对子进程来说不需要监听
			child_process(newfd,client);
		}else{						//父进程,只负责accept
			close(newfd);			//关闭连接socket,对父进程来说不要连接
		}
	}
	close(fd);						//父进程关闭监听socket
	return 0;
}
/* 子进程函数 */
void child_process(int fd,struct sockaddr_in client){
	char buf[30];
	int n;
	while(1){
		memset(buf,0,30);					//清0
		if((n=read(fd,buf,30))<0){			//阻塞等待
			if(n!=EINTR)					//阻塞可能会被中断信号打断,并返回-1
			{								//任何长等待机制都要考虑被中断的处理方式,用来区分异常错误
				perror("read");
				exit(-1);
			}
		}
		if(0 == strcmp(buf,"quit\n")){		//细节:字符串末尾的\n
			printf("client %d ,quit\n",fd);
			close(fd);
			exit(0);
		}
		if(n>0){
			printf("client_ip:%d\n",ntohl(client.sin_addr.s_addr));
			printf("client_port:%d\n",ntohs(client.sin_port));
			printf(">>%s",buf);
		}
	}
}
/* 信号响应函数 */
void handler(int signal){						
	if(SIGCHLD == signal){
		waitpid(-1,NULL,WNOHANG);			//WNOHANG代表非阻塞。
	}
}
发布了15 篇原创文章 · 获赞 0 · 访问量 389

猜你喜欢

转载自blog.csdn.net/mynameisJW/article/details/104540061