【计算机网络】TCP套接字通信
来源:互联网 发布:合作医疗报销软件 编辑:程序博客网 时间:2024/05/31 19:11
本文内容概述:
1>单进程的套接字通信
2>多进程的套接字通信
3>多线程的套接字通信
开始学习Linux基础知识的时候,我们知道Linux下一切皆文件,并且大致可以分为几类文件:普通文件、目录、连接文件、设备和设备文件、套接字、管道。而套接字就是实现网络上进程之间的通信,套接字也是文件。在TCP/IP协议中,IP地址和端口号唯一标识网络中的一个唯一进程,IP地址和端口号就是套接字。
网络中要实现通信,少不了数据的传输。所以这里就引入了网络字节序的概念。
在前边的学习中,我们接触到大端和小端的概念。小端:数据的地位在低地址,高位在高地址;大端:数据的低位在高地址,高位在低地址。网络数据流同样也有大端和小端之分。网络数据流先发出的是低地址,后发出的是高地址。TCP/IP规定,网络数据流采用大端字节序,即就是低位在高地址。我们之所以会说到大端和小端?是因为,网络通信的时候必须知道端口号,如果发送端是大端字节序,接收端是小端字节序,那么最后看到的端口号就是不正确的端口号,所以,我们必须将端口号在发送端和接收端之间转换成统一的字节序形式。
下边提供一组接口:
(一)单进程的套接字通信
服务器:
(1)调用socket,请求系统分配文件描述符。
protocol参数: 与特定的地址家族相关的协议,TCP协议一般为IPPROTO_TCP。也可以写0,那么系统会根据地址格式和套接字类别,自动选择一个适合的协议。
(2)调用bind,绑定本机的信息,包括,IP地址类型,IP地址,端口号。
所以,这里又涉及到字符串与in_addr的转换:
(3)调用listen,监听:
(4)调用accept,接收发送连接请求的socket。
(6)read读取socket中的数据。
(7)通信完成后,调用close关闭套接字。
客户机:
(1)调用socket,请求系统分配文件描述符。
(2)调用connect,连接服务器。
(3)调用read从标准输入中读取数据,放到自定义缓冲区buf中,然后将buf中的数据写到套接字中。
(4)通信完成后,调用close关闭套接字。
代码实现:
//server.c#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>int StartUp(int port,const char* ip){ int ListenSock = socket(AF_INET,SOCK_STREAM,0); if(ListenSock < 0) { perror("socket"); exit(1); } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = inet_addr(ip); if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0) { perror("bind"); exit(2); } if(listen(ListenSock,5) < 0) { perror("listen"); exit(3); } return ListenSock;}int main(int argc,const char* argv[]){ if(argc != 3) { printf("input error\n"); return 1; } int len; int listenSock = StartUp(atoi(argv[2]),argv[1]); struct sockaddr_in client; while(1) { int sock = accept(listenSock,(struct sockaddr*)&client,&len);//获取客户机的信息 if(sock < 0) { perror("accept"); continue; } printf("get a client,ip is %s,port is %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); char buf[1024]; while(1) { ssize_t s = read(sock,buf,sizeof(buf)-1);//服务器进行读数据 if(s > 0) { buf[s] = 0; printf("client# %s\n",buf); } else { //数据已经读完了,客户端不发送数据了 printf("client is quit!\n"); } } close(sock); } return 0;}//client.c#include<stdio.h>#include<sys/socket.h>#include<sys/types.h>#include<unistd.h>#include<arpa/inet.h>int main(int argc,const char* argv[]){ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0) { perror("connect"); return 2; } char buf[1024]; while(1) { printf("send# "); fflush(stdout); //从标准输入读数据,读到buf中,然后从buf写到管道 ssize_t s = read(0,buf,sizeof(buf)-1); if(s < 0) { perror("read"); return 3; } buf[s-1] = 0; write(sock,buf,s); } close(sock); return 0;}
程序运行结果(本地环回测试):
下边我们来模拟一种情形:先运行server,再运行client,client给server发数据,然后ctrl+c终止调server,立即再次启动server,会出现什么现象呢?
这是什么原因呢?服务器终止程序,服务器就是主动发起断开连接请求的一方,根据TCP的3次握手4次挥手协议,主动发起连接断开请求的一方,最后必须等待2MSL的时间确认客户端是否收到自己的确认信息。这里,我们立即运行server的时候,server还是在TIME_WAIT状态,所以bind的时候就会出现地址已经被占用。
解决办法:socket之后,bind之前,加语句
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
就是为了处理这个问题。
网上找来一张关于tcp通信的图:
注意:我们的tcp通信,只是实现了客户端发数据,服务器接收数据。而在实际的应用中,tcp通信是一种全双工通信,即就是任意时刻,客户机可以发送数据,也可以接收数据,服务器可以接收数据,也可以发送数据,这个就需要多进程实现。下边,针对这个tcp通信,我来解决几个问题:
(1)为什么客户端不需要调用bind函数(不是不能,是不必要)?
bind函数是将本地的地址等信息绑定到套接字。而客户端不需要调用bind,这时由操作系统内核自动分配一个动态端口号,通信结束后由操作系统收回。这里的原因还是客户端给出的端口号容易出现冲突问题。
(2).端口号是怎样进行分类的?
端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的端口,逻辑端口是逻辑上区分服务的端口。TCP/IP中的端口就是逻辑端口,范围从0~65535(TCP报文段中用16个比特位表示源端口和目的端口),最多有65536个端口,比如浏览网页的端口是80端口,用于FTP通信的端口是21端口等等。
端口号大致分为3类:
a.系统端口:从0到1023,紧密绑定与一些服务。通常这些端口的通讯明确表明了某种服务的协议,比如21端口总是用于FTP通信。
b.注册端口:从1024~49151.他们松散的绑定于一些服务,也就是说有许多服务绑定于这些端口,这些端口同样用于其他目的。
c.动态/私有端口:从49152~ 65535,不应为服务分配这些端口。
(二)多进程套接字通信
上边的代码,只可以一个客户端进行发送数据,而在实际的应用中,,都是会出现多个客户端给服务器发送数据,所以,上边的实现并不实用。所以,我们可以实现一个多进程的socket通信,以实现多个客户端给服务器发数据。
实现方法:服务器端可以创建多个子进程去处理客户端发来的信息。当每次收到一个新的客户端的连接请求的时候,我们就会fork()出一个子进程,父进程用于等待子进程,子进程用于执行 读客户端发的数据 的操作。细心的你可能会发现,我们在子进程读取信息之前还进行了一次fork(),这是为什么呢?其实,我们用子进程fork()出一个孙子进程,终止掉儿子进程,儿子进程被它的父进程回收,此时的孙子进程就是一个孤儿进程,被1号进程领养。这样做的目的就是,不要让儿子进程等待孙子进程太久而消耗太多的系统资源。
图解:
这里还涉及到父进程关闭通信套接字,子进程关闭监听套接字。这是因为,父进程是来监听的,不需要通信,子进程是读取信息的,不需要监听。
代码实现:
//server.c#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>int StartUp(int port,const char* ip){ int ListenSock = socket(AF_INET,SOCK_STREAM,0); if(ListenSock < 0) { perror("socket"); exit(1); } int opt = 1; setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = inet_addr(ip); if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0) { perror("bind"); exit(2); } if(listen(ListenSock,5) < 0) { perror("listen"); exit(3); } return ListenSock;}int main(int argc,const char* argv[]){ if(argc != 3) { printf("input error\n"); return 1; } int len; int listenSock = StartUp(atoi(argv[2]),argv[1]); struct sockaddr_in client; while(1) { int sock = accept(listenSock,(struct sockaddr*)&client,&len);//获取客户机的信息 if(sock < 0) { perror("accept"); continue; } printf("get a client,ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\ ntohs(client.sin_port)); int id = fork(); if(id > 0) { close(sock); while(waitpid(-1,NULL,WNOHANG) > 0); continue; } else { close(listenSock); if(fork() > 0) { exit(0); } char buf[1024]; while(1) { ssize_t s = read(sock,buf,sizeof(buf)-1);//服务器进行读数据 if(s > 0) { buf[s] = 0; printf("client# %s\n",buf); } else { //数据已经读完了,客户端不发送数据了 printf("client is quit!\n"); break; } } close(sock); // exit(4); break; } } return 0;}//client.c#include<stdio.h>#include<sys/socket.h>#include<sys/types.h>#include<unistd.h>#include<arpa/inet.h>int main(int argc,const char* argv[]){ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0) { perror("connect"); return 2; } char buf[1024]; while(1) { printf("send# "); fflush(stdout); //从标准输入读数据,读到buf中,然后从buf写到管道 ssize_t s = read(0,buf,sizeof(buf)-1); if(s < 0) { perror("read"); break; } buf[s-1] = 0; write(sock,buf,s); } close(sock); return 0;}
(三)多线程的套接字通信
在前边的系统编程的学习中,我们知道线程是进程内部的一个执行流,是在进程的地址空间中运行。而进程是程序的一次动态的执行过程。系统中的进程数过于多的话,会增加系统的负担。所以这里采用线程实现通信。
实现方法:
主线程中创建出一个新线程,新线程的执行函数是读取信息。类似于上边的多进程间的通信,我们可以将新的线程进行分离,分离之后的线程就不需要主线程去等待,而是由操作系统区回收。(这里我们不可以join新线程,如果这样做的话,主线程还是需要花费很长的时间去等待,所以,新的线程还是由系统去回收)
代码实现:
//server.c#include<stdio.h>#include<stdlib.h>#include<netinet/in.h>#include<arpa/inet.h>#include<sys/types.h>#include<sys/socket.h>int StartUp(int port,const char* ip){ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); exit(2); } int opt = 1; setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = inet_addr(ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0) { perror("bind"); exit(3); } if(listen(sock,5) < 0) { perror("listen"); exit(4); } return sock;}void* thread_hander(void* arg){ int sock = *((int*)arg); char buf[1024]; while(1) { ssize_t _s = read(sock,buf,sizeof(buf)-1); if(_s > 0) { buf[_s-1] = 0; printf("client say#%s\n",buf); if(write(sock,buf,sizeof(buf)-1)<0) { break; } } else if(_s == 0) { printf("client is quit!\n"); break; } else { perror("read"); break; } } close(sock);}int main(int argc,const char* argv[]){ if(argc != 3) { printf("input error\n"); return 1; } int listenSock = StartUp(atoi(argv[2]),argv[1]); struct sockaddr_in client; int len = 0; while(1) { int sock = accept(listenSock,(struct sockaddr*)&client,&len); if(sock < 0) { perror("accept"); return 5; } printf("get a client!ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\ ntohs(client.sin_port)); pthread_t tid; int ret = pthread_create(&tid,NULL,thread_hander,&sock); if(ret < 0) { perror("pthread_create"); return 6; } pthread_detach(tid); } return 0;}//client.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>int main(int argc, const char* argv[]){ if(argc != 3) { printf("input error\n"); return 1; } int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); return 2; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); int ret = connect(sock,(struct sockaddr*)&server,sizeof(server)); if(connect < 0) { perror("connect"); return 3; } char buf[1024]; while(1) { printf("send#"); fflush(stdout); ssize_t _s = read(0,buf,sizeof(buf)-1); if(_s > 0) { buf[_s - 1] = 0; if(write(sock,buf,sizeof(buf)-1) < 0) { break; } ssize_t s = read(sock,buf,sizeof(buf)-1); if(s > 0) { buf[s] = 0; printf("server echo#%s\n",buf); } } else { perror("read"); return 4; } } return 0;}
- 【计算机网络】TCP套接字通信
- 计算机网络:TCP套接字通信
- 计算机网络:UDP套接字通信
- TCP套接字通信
- TCP套接字网络通信
- 套接字、UDP通信、TCP通信、TCP\IP协议簇
- 基于TCP通信的套接字Socket
- android传输层tcp套接字通信
- 【计算机网络】网络编程---TCP套接字(一)
- C# .net2.0 套接字编程实例 UDP TCP通信
- C# 2.0 套接字编程实例 UDP TCP通信
- C# .net2.0 套接字编程实例 UDP TCP通信
- C# 实现基本的套接字TCP通信
- 基于TCP套接字实现简单的通信
- 【Linux基础】面向连接的套接字通信(TCP)
- Linux网络编程--TCP的套接字通信学习笔记
- TCP套接字通信中字符串的处理
- 使用TCP/IP的套接字(Socket)进行通信
- Object-Oriented Analysis and Design Using UML 翻译与学习 (十四)
- linux命令
- 第二届2011年国信蓝点杯软件设计大赛预赛的试题1
- AndroidWeekly248期 源码库与代码
- blog learn
- 【计算机网络】TCP套接字通信
- JSP执行过程
- 《智能时代》读后感
- 第二届2011年国信蓝点杯软件设计大赛预赛的试题2
- 解决Emgu.CV.CvInvoke 无法加载 DLL“opencv_core2410”
- 03.表单
- 第二届2011年国信蓝点杯软件设计大赛预赛的试题3
- Canvas适配手机端
- watch命令