unix进程通信方式总结(下)

在前两篇博客http://blog.csdn.net/caoyan_12727/article/details/52049417和http://blog.csdn.net/caoyan_12727/article/details/52126405中对进程间的管道(pipe),命名管道(fifo),消息队列,信号量,信号,共享存储六种进程间的通信方式进行了总结,以上从(1)到(6)讲的是unix系统所提供的经典进程间通信机制(IPC)这些机制允许在同一台计算机上运行的进程可以相互通信。本文将总结unix网络套接字的基本用法!!

unix网络套接字:

如果要和其它计算机上的进程如果要通信(进行数据传输)就要用到网络进程通信(network IPC).网络进程间通信的接口是套接字,进程用该接口能够和其他进程通信,无论它们是在同一台计算机上还是在不同的计算机上,同样的接口即可以用于计算机间通信,也可以用于计算机内通信。套接字接口可以采用许多不同的网络协议进行通信。网络上的博客对套接字的一种比较通俗的比方就是: 套接字连接的过程如同(客户)打一个电话到一个大公司,接线员(服务器进程)接听电话并把它转接到你要找的”部门“(每个部门占用一个端口号用以识,然后接线员(服务器进程)再继续转接其它(客户)的电话。套接字是通信端点的抽象。正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字,套接字描述符在unix系统中被当作是一种文件描述符,事实上,许多处理文件描述符的函数(如read和write)可以用于处理套接字描述符。socket函数是任何套接口网络编程中第一个使用的函数,它向用户提供一个套接字,即套接口描述文件字,它是一个整数,如同文件描述符一样,是内核标识一个IO结构的索引。通过socket函数,我们指定一个套接口的协议相关的属性,为进行使用socket api做好准备。

第一步:创建套接字:

int socket(int domain, int type, int protocol);

其中domain字段的值为:

type字段的类型为:


protocol字段的说明为:


第二步:将套接字与地址关联:

将一个客户端的套接字关联上一个地址没有多大新意,可以让系统选一个默认的地址。然而,对于服务器,需要给一个接收客户端请求的服务器套接字关联上一个总所周知的地址。客户端应有一种方法来发现连接服务器所需要的地址。以下是bind()函数的原型:

int bind(int sockfd,const struct sockaddr * addr,socklen_t len);

参数说明:

sockfd:被绑定的套接字描述符;

addr:被绑定的地址;’

socklen_t:地址长度;

地址的数据结构为:

struct sockaddr {
  unsigned short sa_family; /* address family, AF_xxx */
  char sa_data[14]; /* 14 bytes of protocol address */
  };
sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式,它的值包括四种:AF_UNIX,AF_INET,AF_INET6和AF_UNSPEC。
struct sockaddr_in {
  short int sin_family; /* Address family ,2bytes*/
  unsigned short int sin_port; /* Port number,2bytes */
  struct in_addr sin_addr; /* Internet address,4bytes */
  unsigned char sin_zero[8]; /* Same size as struct sockaddr,填充8个字节 */
  };
  sin_family:指代协议族,在socket编程中只能是AF_INET
  sin_port:存储端口号(使用网络字节顺序)
  sin_addr:存储IP地址,使用in_addr这个数据结构
  sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
而其中in_addr结构的定义如下:
typedef struct in_addr {
  union {
  struct{ unsigned char s_b1,s_b2, s_b3,s_b4;} S_un_b;
  struct{ unsigned short s_w1, s_w2;} S_un_w;
  unsigned long S_addr;
  } S_un;
  } IN_ADDR;
阐述下in_addr的含义,很显然它是一个存储ip地址的共用体有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,

通常的用法是:
int sockfd;
struct sockaddr_in my_addr;//声明结构体存放地址相关信息
sockfd = socket(AF_INET, SOCK_STREAM, 0); 
my_addr.sin_family = AF_INET; /* 主机字节序 */
my_addr.sin_port = htons(MYPORT); /* short, 网络字节序 */
my_addr.sin_addr.s_addr = inet_addr("192.168.0.1");//采用长整形的形式
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
//memset(&my_addr.sin_zero, 0, 8);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。指向sockaddr_in的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向 sockaddr_in的指针转换为指向sockaddr的指针;或者相反。你只要记住,填值的时候使用sockaddr_in结构,而作为函数的 参数传入的时候转换成sockaddr结构就行了,毕竟都是16个字符 长。

第三步:建立连接

如果要处理一个面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKET),那么在开始交换数据前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之间建立一个连接。这个连接就是connect函数完成。

connect()函数用于建立与指定socket的连接。
头文件: #include <sys/socket.h>
函数原型: int connect(int s, const struct sockaddr * name, int namelen);
函数解析:sockfd是由socket函数返回的套接字描述符,第二个和第三个参数分别为一个指向套接字地址结构的的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。如果是TCP套接字, 调用connect函数将激发TCP的三路握手过程 ,而且尽在连接建立成功或出错时才返回:若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。若对客户的SYN响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接(例如服务器进程也许没有正在运行)。硬错误(hard error),客户一接受到RST就马上返回ECONNREFUSED错误。 RST:在TCP发生错误时发送的一种TCP分节,三个条件:目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。客户发出SYN在中间某个路由器引发“destination enreachable”的ICMP错误,则认为是一种“软错误(soft error)”。客户内核保存该消息,并按一定间隔进行重传,若在某个规定时间内仍未收到响应,则把保存的信息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。

第四步:服务器调用listen()函数监听

#include
int listen(int sockfd, int backlog)
返回:0──成功, -1──失败
参数sockfd被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个主动连接的套接字, 也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。参数backlog这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。当调用listen之后,服务器进程就可以调用accept来接受一个外来的请求。

第四步:

调用accept()函数来处理来自客户端的连接请求:

</pre><pre name="code" class="cpp">int accept(int sockfd, struct sockaddr * restrict addr, socklen_t * restrict addrlen);
参数:
sockfd:套接字描述符,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。

第五步:

到目前为止,一个可靠的TCP连接已经建立,接下来就是调用相关函数进行数据的收发;
首先来看看数据的发送函数:

(1)send 函数
int send(sockfd s, const char *buf, int len, int flags );  
该函数的第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。 
unix高级环境编程对send函数是这样描述:

即使send成功返回,也并不表示连接的另一端的进程就一定接收了数据。我们所能保证的只是当send成功返回时,数据已经被无误地发送到网络驱动上。对于支持报文边界的协议,如果尝试发送的单个报文的长度超过协议所支持的最大长度,那么send会失败,并将errno设为EMSGSIZE。对于字节流协议,send会阻塞直到整个数据传输完成。

2. recv函数
int recv( sockfd s, char  *buf, int len, int flags);
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;
(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,
那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于
buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),
recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

下面是一个简单的udp报文传输的客户服务器程序:

客户端:

/*client.c*/
#include<sys/types.h> 
#include<sys/socket.h> 
#include<unistd.h> 
#include<netinet/in.h> 
#include<arpa/inet.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<netdb.h> 
#include<stdarg.h> 
#include<string.h> 
#include<iostream>
using namespace std;
  
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define data_size 512 
int main(){ 
 /* 服务端地址 */
 struct sockaddr_in server_addr; 
 bzero(&server_addr, sizeof(server_addr)); //全部清0
 server_addr.sin_family = AF_INET; //使用ipv4来通信
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//将字符串形式的IP地址转换为按网络字节顺序的整型值,将本机地址设置为服务器地址
 server_addr.sin_port = htons(SERVER_PORT); 
 cout<<"server_port is :"<<SERVER_PORT<<endl;
 cout<<"after conversion is :"<<server_addr.sin_port<<endl;
/* 创建socket */
 int client_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); //返回一个套接字描述符ID
 if(client_socket_fd < 0){
  perror("Create Socket Failed:"); 
  exit(1); 
 } 
 cout<<"fd is :"<<client_socket_fd<<endl; 
 /* 输入文件名到缓冲区 */
 char data[data_size+1]; 
 char buffer[BUFFER_SIZE]; 
 bzero(data, data_size+1); 
 printf("Please Input data On client:"); 
 scanf("%s", data); 

 while(1){
 bzero(buffer, BUFFER_SIZE); //将BUFFER_SIZE全部置0
 strncpy(buffer, data, strlen(data)>BUFFER_SIZE?BUFFER_SIZE:strlen(data)); 
 /* 发送文件名 */
 if(sendto(client_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0) { 
  perror("Send data Failed:"); 
  exit(1); 
 } 
 if(data[0]=='#'){
	printf("data transdering has finished!\n");
	break;
}
 printf("Please Input data On client:"); 
 scanf("%s", data); 
} 
 close(client_socket_fd); 
 return 0; 
} 
服务器端:
/*server.c*/
#include<sys/types.h> 
#include<sys/socket.h> 
#include<unistd.h> 
#include<netinet/in.h> 
#include<arpa/inet.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<netdb.h> 
#include<stdarg.h> 
#include<string.h> 
#include<iostream>
using namespace std;
  
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define data_size 512 
  
int main() { 
 /* 创建UDP套接口 */
 struct sockaddr_in server_addr; 
 bzero(&server_addr, sizeof(server_addr)); 
 server_addr.sin_family = AF_INET; //ipv4协议
 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,
//或“所有地址”、“任意地址”。一般来说,在各个系统中均定义成为0值。
 server_addr.sin_port = htons(SERVER_PORT); 
 cout<<server_addr.sin_port<<endl;
 /* 创建socket */
 int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); 
 if(server_socket_fd == -1) { 
  perror("Create Socket Failed:"); 
  exit(1); 
 } 
 cout<<"the server port is :"<<server_addr.sin_port<<endl;
 /* 绑定套接口 */
 if(-1 == (bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)))) { 
  perror("Server Bind Failed:"); 
  exit(1); 
 } 
  cout<<"server_socket_fd :"<<server_socket_fd<<endl;
 /* 数据传输 */
 while(1) {  
  /* 定义一个地址,用于捕获客户端地址 */
  struct sockaddr_in client_addr; 
  socklen_t client_addr_length = sizeof(client_addr); 
  
  /* 接收数据 */
  char buffer[BUFFER_SIZE]; 
  bzero(buffer, BUFFER_SIZE); 
  if(recvfrom(server_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&client_addr, &client_addr_length) == -1) { 
   perror("Receive Data Failed:"); 
   exit(1); 
  } 
  /* 从buffer中拷贝出file_name */
  char data[data_size+1]; 
  bzero(data,data_size+1); //将data的全部字节置为0
  strncpy(data, buffer, strlen(buffer)>data_size?data_size:strlen(buffer)); 
 if(data[0]=='#'){
	printf("data reciveing has finished!\n");
	break;
	}
  printf("%s\n", data); 
 } 
 close(server_socket_fd); 
 return 0; 
} 
测试结果:




  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值