linux中的socket
Socket是Linux跨进程通信(IPC,Inter Process Communication,详情参考:Linux进程间通信方式总结)方式的一种。相比于其他IPC方式,Socket更牛的地方在于,它不仅仅可以做到同一台主机内跨进程通信,它还可以做到不同主机间的跨进程通信。根据通信域的不同可以划分成2种:Unix domain socket 和 Internet domain socket。
Socket工作示意图和原理图:
图 1 数据处理原理图
图 2 工作流程示意图
Linux中的信号
信号(IPC)最初是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。简单来说信号是操作系统(内核)响应某些条件而产生的一个事件(给进程)。进程之间无法通信,可以使用信号来解决。
信号时由于某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。他们由shell和终端处理器生成来引起中断,他们还可以作为在进程中传递消息或修改行为的一种方式,明确地由一个进程发送给另外一个进程。
linux中常用信号:
设计方案
1.客户端
用户在终端运行客户端程序,命令格式如下:
$>./client <server IP address> [< data.txt]
以上命令格式中服务器IP地址不可省略(一般测试时为127.0.0.1);方括号中的重定向可以没有,当没有重定向文件时,用户键盘输入为客户端发送给服务器的数据来源,每当用户按下回车键时,此行输入会被发送至服务端,并在客户端回显(来自服务端的回应),当用户在键盘上按下Ctrl D时,客户端程序结束;当有重定向文件时,客户端将文件中数据逐行发送至服务端,发送完毕且回显完毕结束程序,全过程用户无需键盘输入。
2.服务端
服务器程序在终端中以如下格式后台运行:
$>./server &
主进程做好套接字准备后会阻塞在accept上等待哭护短请求到达;当客户端请求到达时,服务器主进程需要创建子进程为客户端服务;结束时子进程退出,主进程通过信号处理调用waitpid回收子进程;服务器可以同时处理多个客户端的请求。
具体实现
1.客户端与服务器的套接字初始化配置
图 4客户端socket配置
图 5服务器socket配置
3.客户端数据传输
无重定向文件时,因为控制终端(/dev/tty)如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。所以能够在当前终端直接进行输入,不满足参数为3个所以不进行重定向,通过read读取终端输入的数据读到buf里面,buf相当于中间变量,然后在将buf里的数据write写到套接字socket_fd里面,然后清空一下buf,然后下边一个if判断,观察的是服务器有没有回传数据,如果有的话就在终端上打印出来,(向服务器写着,客户端读者)
图 11 无重定向文件测试
有重定向文件时,argc == 3,所以进行重定向,现在开始指向终端的文件描述符指向data_fd指向的地方,及重定向文件,所以图()的fd指的是data_fd,将重定向文件一行一行读出来放到buf进行传输,传输完成之后,回显到客户端终端
图 14 重定向文件的测试
4.创建多个进程和回收进程
只要有一个连接就会fork一个子进程,然后这样就能实现多个客户端共同与服务器进行通信的目的,里面对数据的处理都是相同的,pid == 0时就是在子进程里,pid > 0,就是在父进程里面,在父进程里面当接收到SIGCHLD信号时,调用catch_child函数(内核调用),在函数里通过waitpid回收子进程,因为每次都只能回收一个子进程,所有要使用while循环,回收之后打印一下回收的子进程号。
图 15 服务器创建进程和处理数据核心代码
图 16 回收子进程的函数(内核调用)
图 17 服务器的终端显示
开始时一个server进程(最下边)
图 18 开启服务器,客户端不开启
通过ps –elf我们看到数据传输结束还是只有一个server进程,证明我们的子进程回收程序正常运行了。
图 19 开启服务器和客户端
客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
int port = 52347;
int socket_fd= socket(AF_INET,SOCK_STREAM,0);
if(socket_fd == -1)
{
printf("socket_fd failed:%d\n",errno);
}
struct sockaddr_in dest_addr; // 2.目标地址信息,连接
bzero(&dest_addr,sizeof(struct sockaddr_in)); //初始化
dest_addr.sin_family = AF_INET; //协议
dest_addr.sin_port = htons((unsigned short)port); //目标地址端口号
dest_addr.sin_addr.s_addr = inet_addr(argv[1]); //服务器IP
connect(socket_fd,(struct sockaddr*)&dest_addr,sizeof(dest_addr));
//操作
char S_buf[256] = "";
char R_buf[256] = "";
int fd = open("/dev/tty",O_RDWR);
if(fd == -1)
{
printf("fd failed:%d\n",errno);
}
int data_fd;
if(argc == 3)
{
data_fd = open(argv[2], O_RDWR);
dup2(data_fd, fd); //后-->前
}
int num;
while(1)
{
if((num = read(fd, S_buf, 256)) > 0)
{
write(socket_fd, S_buf, num);
memset(S_buf, 0, 256);
if(read(socket_fd,R_buf,256) > 0)
{
printf("%s\n",R_buf);
memset(R_buf, 0, 256);
}
}
else
{
return 0;
}
}
close(fd);
close(data_fd);
//close(socket_fd);
return 0;
}
服务器代码:
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include<string.h>
#include <fcntl.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
void catch_child(int signo)
{
pid_t wpid;
while ((wpid = waitpid(-1, NULL, WNOHANG)) > 0)
printf("child %d exit with \n", wpid);
}
int main()
{
int port = 52347;
int socket_fd= socket(AF_INET,SOCK_STREAM,0); /* 1.建立socket */
if(socket_fd == -1)
{
printf("socket_fd failed:%d",errno);
}
struct sockaddr_in server_addr; // 2.目标地址信息,连接
bzero(&server_addr,sizeof(struct sockaddr_in)); //初始化
server_addr.sin_family = AF_INET; //协议
server_addr.sin_port = htons((unsigned short)port); //目标地址端口号
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器IP
int ret = bind(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret == -1) //绑定地址结构体和socket
{
printf("bind error");
return -1;
}
listen(socket_fd,5);
//操作ser
int connect_fd;
char buf[256] = "";
int num;
while(1)
{
connect_fd = accept(socket_fd, NULL, NULL);
if(connect_fd==-1){
printf("accept error");
exit(1);
}
pid_t pid = fork();
if(pid == 0)
{
while(1)
{
if((num = read(connect_fd, buf, 256)) > 0)
{
write(connect_fd, buf, num);
memset(buf, 0, 256);
}
else
{
printf("回显完成!\n");
return 1;
}
}
}
else if(pid > 0)
{
signal(SIGCHLD,catch_child);
}
}
return 0;
}