P2P服务器 - 客户端实现(多进程)

本文详细介绍了如何使用Linux Socket编程构建一个P2P服务器客户端,包括服务器端的socket创建、地址绑定、监听、接受连接以及读写操作,客户端的连接、读写操作。通过多进程实现读写分离,实现两端的数据传输。当客户端断开连接时,服务器端读进程会检测到并退出。
摘要由CSDN通过智能技术生成

在熟悉Linux Socket编程的时候,照着视频课程敲了一个P2P服务器客户端,实现了两端都能读写操作。
在这里插入图片描述

服务器端

服务器端创建socket()

首先创建socket(),socket 文件描述符叫做listen_fd

 //创建socket
    int listen_fd = socket(PF_INET,SOCK_STREAM,0);
    if (listen_fd < 0)
        Error("socket()");
socket()  RETURN
 -1 error
 fd value
构建sockaddr_in结构体
	//socket address socket地址
    struct sockaddr_in addr;
    // memset(&addr,0,)   
    bzero(&addr,sizeof(addr));  //初始化内存
    addr.sin_family = AF_INET;	
    addr.sin_port = htons(23344); // 转网络字节序
    addr.sin_addr.s_addr = htonl(INADDR_ANY); //任意地址

设置socket reuseaddr
	//设置socket reuseaddr 重复使用
    int on = 1;
    int optret = setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if (optret < 0)
        Error("setsockopt()");
bind socket

说明:bind socket_fd with address

//bind socket
    int bindret = bind(listen_fd,(const sockaddr *)&addr,sizeof(addr));
     if (bindret < 0)
        Error("bind()");
监听socket fd
    //监听 监听队列长度5
    int listenret = listen(listen_fd, 5);
    if (listenret < 0)
        Error("listen()");
accpte() 接受 client 连接

这个地方会阻塞。返回的 conn_fd 就是client端的socket fd

 //accpte 接受client 连接   这个地方会阻塞  返回的client socket 的fd 到时候用这个向client去写
    struct sockaddr_in clientaddr;
    socklen_t clientaddrlen = sizeof(clientaddr);
    int conn_fd = accept(listen_fd,(struct sockaddr *)&clientaddr,&clientaddrlen);
    if (conn_fd < 0)
        Error("accept()");
服务器读写部分

对于读写操作的话,这部分是阻塞的,操作的时候回阻塞对应的进程或者线程。这里我们选择进程方案:一个进程负责写,一个进程负责读。需要指出的是,所操作的fd一定是client端的。

 write(conn_fd,sendbuf,sizeof(sendbuf));
 int ret = read(conn_fd, recvbuf, sizeof(recvbuf));
读进程(接收进程)

这部分逻辑分支,可以选择一个while死循环,因为read是阻塞操作,在请求读的时候进程是等待状态的。读是从内核数据缓冲区中拷贝数据出来。

		char recvbuf[1024] = {0};
        while (1)
        {
            bzero(recvbuf, sizeof(recvbuf));
            int ret = read(conn_fd, recvbuf, sizeof(recvbuf));
            if  (ret == -1)
            {
                Error("read()");
            }
            else if (ret == 0)
            {
                printf("peer close.\n");
                break;
            }
            // ret > 0
            fputs(recvbuf,stdout);
        }
对端断开连接

当对端(客户端)断开连接,server端read()会返回0,意味着对端断开连接。
此时我们可以终止服务器端进程了。这里我们通过发送SIGUSR1信号的方式来触发进程退出。

写进程(发送进程)

使用一个while+fgets方式,获取stdin的输入,这里fgets其实我理解也是一个阻塞的方式。没有数据就等待,跟C语言中getchar()是一样的。

		char sendbuf[1024]= {0};
        //从标准输入流中读  也就是键盘输入 size -1 长度到buff buff的空间已经分配好了
        //不可能是NULL 所以会一直循环 
        while ( (fgets(sendbuf,sizeof(sendbuf),stdin)) != NULL)
        {
            write(conn_fd,sendbuf,sizeof(sendbuf));
            bzero(sendbuf,sizeof(sendbuf));
        }
服务器退出

这里我们用到了SIGUSR1 用户自定义信号 默认处理:进程终止
当一个进程调用fork时,因为子进程在开始时复制父进程的存储映像,信号捕捉函数的地址在子进程中是有意义的,所以子进程继承父进程的信号处理方式
但是当子进程调用exec后,因为exec运行新的程序后会覆盖从父进程继承来的存储映像,那么信号捕捉函数在新程序中已无意义,所以exec会将原先设置为要捕捉的信号都更改为默认动作。

接收到SIGUSR1信号,我们就退出进程。

 signal(SIGUSR1,SignalHandler); //在主线程设置signal SIGUSR1的handler
 signalHandler //记住 签名void SignalHandler(int sig )
 void signalHandler(int sig){
	printf("recv sig: %d\n",sig);     
    printf("child process closed.'cause parent process closed.\n");  //打印日志
	exit(EXIT_SUCCESS); //进程退出
}

软中断信号
我们平时在程序运行的时候按下ctrl-c、ctrl-z或者kill一个进程的时候,其实都是内核向这个进程发送了一个特定信号,当进程捕获到信号后,进程会被中断并立即跳转到信号处理函数。默认情况下一个程序对ctrl-c发出的信号(SIGINT)的处理方式是退出进程。
收到信号有几种处理办法:

  1. 处理信号 signalHandler
  2. 忽略信号
  3. 保持信号默认操作

客户端

客户端创建socket
int listen_fd = socket(PF_INET,SOCK_STREAM,0);
    if (listen_fd < 0)
        Error("socket()");
构建sockaddr_in结构体

你要和对方连接,一定是对方的ip和端口

struct sockaddr_in addr;
    // memset(&addr,0,)
    bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1",&addr.sin_addr);
    addr.sin_port = htons(23344);
connect

客户端的操作很简单,就是一个connect(fd,addr)

   // listen_fd
    int conn = connect(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
    if (conn < 0)
        Error("connect()");
客户端代码

代码使用多进程的方式,主进程负责recv操作(读),子进程负责send操作(写)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <vector>
#include <sys/epoll.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <algorithm>
#include <sys/types.h>

#define SIZE 1024
int main() {
    int sock_fd = socket(PF_INET,SOCK_STREAM,0);
    assert(sock_fd >0);
        struct sockaddr_in addr;
    // memset(&addr,0,)
    bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1",&addr.sin_addr);
    addr.sin_port = htons(23344);
    int conn = connect(sock_fd,(struct sockaddr*)&addr,sizeof(addr));
    assert(conn >= 0);
    printf("ret: %d client IP: %s Port %d create_socket: %d\n",conn, inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),sock_fd);

    pid_t pid;

    if (0 >(pid = fork())){
        perror("fork");
        return -1;
    }
    else if(0 == pid){
        char sendbuf[SIZE] = {'0'};
        while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL){
            write(sock_fd,sendbuf,sizeof (sendbuf));
            bzero(sendbuf,sizeof (sendbuf));
        }
    }
    else{
        char recvbuf[SIZE] = {'0'};

        while (1) {
            read(sock_fd, recvbuf, sizeof(recvbuf));
            fputs(recvbuf, stdout);

        }
    }


    close(sock_fd);

    return 0;
}

总结

P2P这个结构可以用来实现,最简单的局域网聊天室的建立。这里时时使用fork()子进程的方式去实现的。其实也可以用多线程的方式去实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值