Linux——TCP编程通信及编程模型

一.TCP的编程模型
    回顾:

        UDP模型的UML图(一对多)


        TCP模型的UML图(一对一)


    案例1:
    TCP的服务器(在案例中使用浏览器作为客户程序)        
    socket建立服务器的文件描述符号缓冲
    bind把IP地址与端口设置到文件描述符号中
    listen负责根据客户连接的不同IP与端口,负责生成对应的文件描述符号及其信息

    accept一旦listen有新的描述符号产生就返回,否则阻塞。

tcp_service.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
main()
{
	int serverfd;//总的(服务器)fd
	int cfd;//客户fd
	int a;
	struct sockaddr_in sadr;//服务器地址
	struct sockaddr_in cadr;//每一个连接者的地址
	socklen_t len;
	int r;
	char buf[1024];
	//1.socket
	serverfd=socket(AF_INET,SOCK_STREAM,0);
	if(serverfd==-1) printf("1:%m\n"),exit(-1);
	printf("建立服务器socket成功!\n");
	//2.bind
	sadr.sin_family=AF_INET;
	sadr.sin_port=htons(9999);
	inet_aton("192.168.1.116",&sadr.sin_addr);
	r=bind(serverfd,
			(struct sockaddr*)&sadr,sizeof(sadr));
	if(r==-1) printf("2:%m\n"),exit(-1);
	printf("服务器地址绑定成功!\n");
	
	//3.listen只要有一个新的客户连上来或发送数据 listen就为该客户分配一个内存节点
	r=listen(serverfd,10);
	if(r==-1) printf("3:%m\n"),exit(-1);
	 printf("监听服务器成功!\n");
	
	//4.accept把listen分配内存的描述符取出来 可以用循环接收多个节点
        //这里只接收一个节点(代理客户描述符号)用于测试  
      
        len=sizeof(cadr);
	 cfd=accept(serverfd,
			 (struct sockaddr*)&cadr,&len);
	 printf("有人连接:%d,IP:%s:%u\n",
			 cfd,inet_ntoa(cadr.sin_addr),
			 ntohs(cadr.sin_port));		
	
	//5.处理代理客户描述符号的数据
	 while(1)
	{
		 r=recv(cfd,&a,4,MSG_WAITALL);//4字节缓冲填满之后才返回		
		 if(r>0)
		  {
			  //buf[r]=0;
			 printf("::%d\n",a);
		 }
		
		 if(r==0)
		{
			 printf("连接断开!\n");
			 break;
		}
		if(r==-1)
		{
			 printf("网络故障!\n");
			 break;
		}
	 }
	close(cfd);
	close(serverfd);
}






二.TCP通信特点(相对于UDP)
    案例3:
        有连接:只要连接后,发送数据不用指定IP与端口
        数据无边界:TCP数据流,非数据报文.
        描述符号双工:

        数据准确:TCP协议保证数据完全正确

tcp_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
main()
{
	int fd;
	struct sockaddr_in addr;
	int r;
	int i=0;
	//1.socket
	fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd==-1) printf("1:%m\n"),exit(-1);
	printf("建立socket成功!\n");
	//2.connect
	addr.sin_family=AF_INET;
	addr.sin_port=htons(9999);
	inet_aton("192.168.1.116",&addr.sin_addr);
	r=connect(fd,
			(struct sockaddr*)&addr,sizeof(addr));
	if(r==-1) printf("2:%m\n"),exit(-1);
	printf("连接服务器成功!\n");
	
	
	for(i=0;i<20;i++)
	{
		send(fd,"hello",5,0);//数据没有边界的话 一个数据(结构体)可能分多次接收 
				     //不一定接收20次 如果用UDP就是接收20次
	}
}


    案例4:
        使用TCP发送数据注意:
            不要以为固定长的数据,一定接收正确,要求使用MSG_WAITALL来保证
        
    案例5:
        TCP数据发送的分析:
            基本数据int short long float  double
            结构体数据struct
            建议使用MSG_WAITALL
            字符串数据以及文件数据等不固定长度的数据怎么发送?
                
        制定数据包:
            头:大小固定(数据大小)
            体:大小变化(数据)        
    案例6:
        使用TCP传送文件
        定义文件数据包.
                int 数据大小;
                char[]数据
                        
        传递文件名
        传递数据(循环)

        传递0长度的数据表示文件结束

domo1Client.c//发送端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

main()
{
	int sfd;
	int ffd;
	int size;
	int r;
	
	int  len;
	char buf[128];
	
	struct sockaddr_in dr;
	char filename[]="udp_a.c"; 
	
	//1.建立socket
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(sfd==-1) printf("1:%m\n"),exit(-1);
	printf("socket成功!\n");
	//2.连接到服务器
	dr.sin_family=AF_INET;
	dr.sin_port=htons(9988);
	inet_aton("192.168.1.116",&dr.sin_addr);
	r=connect(sfd,(struct sockaddr*)&dr,sizeof(dr));
	if(r==-1) printf("2:%m\n"),close(sfd),exit(-1);	
	printf("connect成功!\n");
	//3.打开文件
	ffd=open(filename,O_RDONLY);
	if(ffd==-1) printf("3:%m\n"),close(sfd),exit(-1);
	printf("open文件成功!\n");
	//4.发送文件名
	len=strlen(filename);	
	r=send(sfd,&len,sizeof(len),0);//发送文件名长度
	r=send(sfd,filename,len,0);//发送文件名 
	if(r==-1)
	printf("4:%m\n"),close(ffd),close(sfd),exit(-1);
	printf("发送文件名成功!\n");
	//5.循环发送数据
	while(1)
	{
		size=read(ffd,buf,128);
		if(size==-1) break;
		if(size==0) break;
		if(size>0)
		{
			//发送数据长度
			r=send(sfd,&size,sizeof(size),0);
			if(r==-1) break;
			//发送数据
			r=send(sfd,buf,size,0);
			if(r==-1) break;
		}
	}
	//6.读取到文件尾,发送0数据包
	size=0;
	r=send(sfd,&size,sizeof(size),0);
	close(ffd);
	close(sfd);
	printf("OK!\n");
}

demo1Server.c//接收端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
main()
{
	int sfd,cfd,ffd;
	int r;
	int len;
	char buf[128];
	char filename[100];
	struct sockaddr_in dr;
	//1.建立服务器socket
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(sfd==-1) printf("1:%m\n"),exit(-1);
	printf("建立服务器成功!\n");
	//2.绑定IP地址与端口
	dr.sin_family=AF_INET;
	dr.sin_port=htons(9988);
	dr.sin_addr.s_addr=inet_addr("192.168.180.92");
	r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
	if(r==-1) printf("2:%m\n"),close(sfd),exit(-1);
	printf("绑定地址成功!\n");
	//3.监听
	r=listen(sfd,10);
	if(r==-1) printf("3:%m\n"),close(sfd),exit(-1);
	printf("监听成功!\n");
	//4.接收连接
	cfd=accept(sfd,0,0);
	if(cfd==-1) printf("4:%m\n"),close(sfd),exit(-1);
	printf("开始接收文件!\n");
	//5.接收文件名
	r=recv(cfd,&len,sizeof(len),MSG_WAITALL);
	printf("文件名长度:%d\n",len);
	r=recv(cfd,filename,len,MSG_WAITALL);
	filename[len]=0;
	printf("传递的文件名是:%s\n",filename);
	//6.创建文件
	ffd=open(filename,O_RDWR|O_CREAT,0666);	
	//7.循环接收文件数据
	while(1)
	{
		r=recv(cfd,&len,sizeof(len),MSG_WAITALL);
		if(len==0) break;
		r=recv(cfd,buf,len,MSG_WAITALL);
		
		write(ffd,buf,len);
		
	}
	close(ffd);
	close(cfd);
	close(sfd);
	printf("接收文件完毕!\n");
}
          

三.TCP服务器编程模式
        TCP的服务器端维护多个客户的网络文件描述符号.
        对服务器多个客户描述符号同时做读操作,是不可能.需要多任务模型完成.
        多任务模型?
        1.多进程
        2.IO的异步模式(select模式/poll模式)
        3.多线程模式        
        4.多进程池
        5.线程池

四.综合应用--多进程应用
        1.怎样使用多进程

        2.多进程的缺陷,以及怎么解决


服务器端

建立socket
            绑定地址
            监听
            循环接收客户连接
            为客户创建子进程
            在子进程接收该客户的数据,并且广播

chatServer.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mman.h>
int sfd;
int *fds;//存放所有客户代理描述符号
int idx=0;//客户在数组中下标
struct sockaddr_in dr;
int r;
main()
{
	//1.建立服务器 socket
	fds=mmap(0,4*100,PROT_READ|PROT_WRITE,
		MAP_ANONYMOUS|MAP_SHARED,0,0);
	bzero(fds,sizeof(fds));
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(sfd==-1) printf("1:%m\n"),exit(-1);	
	printf("socket OK!\n");
	//2.绑定地址
	dr.sin_family=AF_INET;
	dr.sin_port=htons(9989);
	dr.sin_addr.s_addr=inet_addr("192.168.1.116");
	r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
	if(r==-1) printf("2:%m\n"),exit(-1);
	printf("bind ok!\n");
	//3.监听
	r=listen(sfd,10);
	if(r==-1) printf("3:%m\n"),exit(-1);
	printf("listen ok!\n");
	//4.循环接收客户连接
	while(1)
	{
		fds[idx]=accept(sfd,0,0);
		if(fds[idx]==-1) break;
		printf("有客户连接:%d\n",fds[idx]);
		//5.建立一个子进程
		if(fork())
		{
			idx++;
			continue;
		}
		else
		{		
			//6.子进程任务:接收客户数据并且广播
			char buf[256];
			int i;
			printf("开始接收客户数据:%d\n",fds[idx]);
			while(1)
			{
				//接收客户数据
				r=recv(fds[idx],buf,255,0);
				printf("%d\n",r);
				if(r==0)
				{
					printf("有客户退出\n");
					close(fds[idx]);
					fds[idx]=0;
					break;					
				}
				if(r==-1)
				{
					printf("网络故障\n");
					close(fds[idx]);
					fds[idx]=0;
					break;
				}
				buf[r]=0;
				printf("来自客户的数据:%s\n",buf);
				//广播
				for(i=0;i<100;i++)
				{
					if(fds[i]>0)
					{
						send(fds[i],buf,r,0);
					}	
				}
			}
			exit(0);
		}
	}
	close(sfd);	
}
 客户端
            2.1.建立socket
            2.2.连接服务器
            2.3.创建CURSES界面
            2.4.创建子进程
            2.5.在父进程中,输入,发送聊天信息

            2.6.在子进程中,接收服务器传递其他客户聊天信息


chatClient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.<span style="font-size:18px;">建立socket
            绑定地址
            监听
            循环接收客户连接
            为客户创建子进程
            在子进程接收该客户的数据,并且广播</span>h>
#include <curses.h>
#include <signal.h>
WINDOW*winfo,*wmsg;
int fd;
int r;
struct sockaddr_in dr;
int isover=1;

int initSocket();
void initUI(); 
void destroy();
void handle(int s)
{
	int status;
	wait(&status);
	destroy();
	exit(-1);	
}
main()
{	
	//printf("网络初始化成功!\n");
	initUI();
	r=initSocket();	
	if(r==-1) exit(-1);
	signal(SIGCHLD,handle);
	if(fork())
	{
		//输入,发送
		char buf[256];
		while(1)
		{
			mvwgetstr(wmsg,1,1,buf);
			//buf[r]=0;
			send(fd,buf,strlen(buf),0);			
			//wclear(wmsg);
			//box(wmsg,0,0);
			refresh();
			wrefresh(wmsg);
			wrefresh(winfo);
		}
	}
	else
	{
		
		//接收,显示
		char buf[256];
		int line=1;
		while(1)
		{			
			r=recv(fd,buf,255,0);
			if(r==-1) break;
			if(r==0) break;
			buf[r]=0;
			mvwaddstr(winfo,line,1,buf);
			line++;			
			if(line>=(LINES-3))
			{
				wclear(winfo);
				line=1;
				box(winfo,0,0);				
			}
			
			
			wmove(wmsg,1,1);
			touchwin(wmsg);
			refresh();
			wrefresh(winfo);			
			wrefresh(wmsg);
		}
		exit(-1);
	}
		
	destroy();
}
void destroy()
{
	close(fd);
	endwin();
}
void initUI()
{
	initscr();
	winfo=derwin(stdscr,(LINES-3),COLS,0,0);
	wmsg=derwin(stdscr,3,COLS,LINES-3,0);
	keypad(stdscr,TRUE);
	keypad(wmsg,TRUE);
	keypad(winfo,TRUE);
	box(winfo,0,0);
	box(wmsg,0,0);
	refresh();
	wrefresh(winfo);
	wrefresh(wmsg);
}
int initSocket()
{
	fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd==-1) return -1;
		
	dr.sin_family=AF_INET;
	dr.sin_port=htons(9989);
	dr.sin_addr.s_addr=inet_addr("192.168.1.116");
	r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
	if(r==-1)
	{
		close(fd);
		return -1;
	}
	return 0;
}
       

           

总结:
        1.TCP的四大特点
        2.TCP的数据接收:固定长与变长数据的接收
        3.TCP的服务器多进程处理
            问题:多进程由于进程资源结构独立.
            新进程的文件描述符号的上下文环境在老进程无法访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值