前言
UNIX/Linux 是多任务的操作系统,通过多个进程分别处理不同事务来实现,如果多个进程要进行协同工作或者争用同一个资源时,互相之间的通讯就很有必要了
进程间通信,Inter process communication,简称 IPC,在 UNIX/Linux 下主要有以下几种方式:
- 无名管道 ( pipe )
- 有名管道 ( fifo )
- 信号 ( signal )
- 信号量 ( semaphore )
- 消息队列 ( message queues )
- 共享内存 ( shared memory )
- 套接字 ( socket )
这里分享一下我在学习进程通讯过程中的笔记和心得
概要
套接字
之前的各种通信机制如:pipe,FIFO,message queue,signal ,semaphore ,shared memory 都局限于同一台计算机上的进程间通信
但是要实现不同计算机(通过网络相连)上的进程互相通信,就需要网络进程间通信(network IPC)
套接字允许进程与不同计算机上的以及同一计算机上的其它进程通信
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务
代码示例
要求
编写一个网络通讯程序,客户端通过指定IP地址的方式向服务端发送一段字符串,服务端收到后显示并且作出响应,然后退出
代码示例
tcpserver.c
#include <stdio.h> //perror,printf 相关函数在此声明
#include <netinet/in.h> //sockaddr_in,htons,htonl,socket,AF_INET,SOCK_STREAM,INADDR_ANY,SOL_SOCKET,SO_REUSEADDR,bind,listen,accept,recv,send 相关声明和定义在这个文件中
#include <string.h> //memset 相关函数在此声明
#include <unistd.h> //close 相关函数在此声明
#define MAX_CONN 2
#define BUF_SIZE 1024
#define PORT 9000
int main()
{
struct sockaddr_in server_sai,client_sai;
int sfd=0,cfd=0,res=-1,on=1,recvbytes=0,sendbytes=0;
int addrlen=sizeof(struct sockaddr);
char buf[BUF_SIZE]; //各种变量定义与初始化
if(-1 == (sfd=socket(AF_INET,SOCK_STREAM,0))) //创建一个IPV4的TCP socket
{
perror("socket");
return res;
}
server_sai.sin_family=AF_INET; //IPV4 协议族
server_sai.sin_port=htons(PORT); //9000端口
server_sai.sin_addr.s_addr=htonl(INADDR_ANY); //0.0.0.0 的通配监听
memset(&(server_sai.sin_zero),0,sizeof(server_sai.sin_zero)); //将剩余部分填零
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket
if(-1 == bind(sfd,(struct sockaddr *)&server_sai,sizeof(struct sockaddr))) //将 sfd 和 socket 地址进行绑定
{
perror("bind");
return res;
}
if (-1 == (listen(sfd,MAX_CONN))) //在sfd上进行监听,最多允许同时有2个请求在队列中排队,此配置正是DDOS的攻击点,协议天然的缺陷在于,不论这个值设多设少,都不会是一个适合的值
{
perror("listen");
return res;
}
else printf("Listening...n");
if(-1 == (cfd=accept(sfd,(struct sockaddr *)&client_sai,(socklen_t *)&addrlen))) //接受连接,将返回的描述符赋给cfd
{
perror("accept");
return res;
}
memset(buf,0,sizeof(buf)); //将缓存置零
if(-1 == (recvbytes = recv(cfd,buf,BUF_SIZE,0))) //从对端接受内容并且存到buf中
{
perror("recv");
return res;
}
printf("Received a message:%sn",buf); //将收到的内容输出
if(-1 == (sendbytes = send(cfd,"OK",2,0))) //给客户端回复一个ok
{
perror("send");
return res;
}
close(sfd);
close(cfd); //进行清理,关闭打开的描述符
res=0;
return res;
}
tcpclient.c
#include <stdio.h> //printf,sprintf,perror 相关声明在此文件中
#include <string.h> //memset,strlen
#include <unistd.h> //close
#include <arpa/inet.h> //sockaddr_in,socket,AF_INET,SOCK_STREAM,htons,inet_addr,connect,sockaddr,send,recv //相关定义和声明在此文件中
#define MAX_CONN 2
#define BUF_SIZE 1024
#define PORT 9000
int main(int argc,char *argv[])
{
struct sockaddr_in server_sai;
int sfd=0,res=-1,recvbytes=0,sendbytes=0;
char buf[BUF_SIZE],buf2[5]={0}; //进行变量的定义和初始化
if(argc < 3) //如果参数小于3个就报错,命令后面会分别加上IP地址和消息内容,所以一共是三个参数
{
printf("error number of argc:%dn",argc);
return res;
}
memset(buf,0,sizeof(buf)); //对buf清零
sprintf(buf,"%s",argv[2]); //将要传输的内容(第二个参数)复制到buf中
if(-1 == (sfd=socket(AF_INET,SOCK_STREAM,0))) //创建一个IPV4的TCP socket
{
perror("socket");
return res;
}
server_sai.sin_family=AF_INET; //IPV4 协议族
server_sai.sin_port=htons(PORT); //9000端口
server_sai.sin_addr.s_addr=inet_addr(argv[1]); //使用第一个参数作为IP地址
memset(&(server_sai.sin_zero),0,sizeof(server_sai.sin_zero)); //将结构体剩余部分填零
if (-1 == connect(sfd,(struct sockaddr *)&server_sai,sizeof(struct sockaddr))) //使用sfd进行连接
{
perror("connect");
return res;
}
if (-1 == (sendbytes=send(sfd,buf,strlen(buf),0))) //将buf中的内容写到远端服务端,buf中的内容是命令行中的第二个参数
{
perror("send");
return res;
}
recvbytes=recv(sfd,buf2,5,0); //从服务端接收数据,写到buf2中
printf("%d -->%sn",recvbytes,buf2); //将buf2中的数据显示出来
close(sfd); //进行清理工作,关闭描述符
res=0;
return res;
}