您的位置:首页 > 理论基础 > 计算机网络

练习:TCP多进程通信

2020-03-08 14:31 1426 查看

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代表非阻塞。
}
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
Jan___ 发布了12 篇原创文章 · 获赞 0 · 访问量 342 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: