首页 > 代码库 > Socket 编程实践(5) --p2p聊天程序设计与实现

Socket 编程实践(5) --p2p聊天程序设计与实现

一个短连接的client

//短链接客户端
int main()
{
    int loopCount = 20;

    char sendBuf[BUFSIZ] = {0};
    char recvBuf[BUFSIZ] = {0};

    for (int i = 0; i < loopCount; ++i)
    {
        sprintf(sendBuf,"Hello Server %d\n",i);

        //创建新的套接字
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if (sockfd == -1)
        {
            err_exit("socket error");
        }
        //填写好服务器地址及其端口号
        struct sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(8002);
        //本机测试,填写127.0.0.1(回环地址)
        serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        //连接server
        if (connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
        {
            err_exit("connect error");
        }

        //向server发送数据
        if (write(sockfd,sendBuf,strlen(sendBuf)) == -1)
        {
            err_exit("write socket error");
        }

        int readCount = 0;
        //从server接收数据
        if ((readCount = read(sockfd,recvBuf,sizeof(recvBuf))) < 0)
        {
            err_exit("read socket error");
        }
        //将其回写到终端
        write(STDOUT_FILENO,recvBuf,strlen(recvBuf));

        close(sockfd);
    }

    return 0;
}


注:server端基于上一篇博客中的代码


点对点聊天程序设计与实现

点对点聊天程序功能说明:

 

 

代码实现与说明

//serever端完整代码与说明
#include "commen.h"

int main()
{
    //安装信号接收函数
    signal(SIGUSR1,onSignal);

    //创建监听套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if (sockfd == -1)
    {
        err_exit("socket error");
    }

    //添加地址复用
    int optval = 1;
    if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1)
    {
        err_exit("setsockopt SO_REUSEADDR error");
    }

    //将监听套接字绑定本机
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;    //该行可以删除
    serverAddr.sin_port = htons(8001);
    serverAddr.sin_addr.s_addr = INADDR_ANY;    //绑定本机的任意一个IP地址
    if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
    {
        err_exit("bind error");
    }

    //启用监听套接字
    //一旦调用了listen,则sockfd编程被动套接字 -> 等待客户端的连接(只能接受连接,不能发送连接)
    if (listen(sockfd,SOMAXCONN) == -1)
    {
        err_exit("listen error");
    }

    //如果没有客户端链接的来到,则该系统调用会一直阻塞
    struct sockaddr_in peerAddr;
    socklen_t peerLen = sizeof(peerAddr);
    int peerSockfd = accept(sockfd, (struct sockaddr *)&peerAddr,&peerLen);
    if (peerSockfd == -1)
    {
        err_exit("accept error");
    }

    //客户端来到
    cout << "Client Connected:" << endl;
    cout << "\tpeerAddr.sin_addr: " << inet_ntoa(peerAddr.sin_addr) << endl;
    cout << "\tpeerAddr.sin_port: " << ntohs(peerAddr.sin_port) << endl;

    //创建子进程
    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    //父进程:从客户端接收数据 -> 打印到屏幕
    if (pid > 0)
    {
        close(STDIN_FILENO);    //关闭没用的文件描述符
        char recvBuf[BUFSIZ] = {0};
        int readCount = 0;
        //从客户端接收数据
        while ((readCount = read(peerSockfd,recvBuf,sizeof(recvBuf))) > 0)
        {
            recvBuf[readCount] = 0;
            //将其打印到屏幕
            fprintf(stdout,"%s",recvBuf);
            memset(recvBuf,0,sizeof(recvBuf));
        }
        //对端已经关闭
        if (readCount == 0)
        {
            close(peerSockfd);  //关闭通信结点
            cout << "peer connect closed" << endl;

            //发送SIGUSR1信号给子进程,将子进程杀死
            kill(pid,SIGUSR1);
            _exit(0);
        }
        //接收错误
        else if (readCount == -1)
        {
            close(peerSockfd);
            //发送SIGUSR1信号给子进程,将子进程杀死
            kill(pid,SIGUSR1);

            err_exit("read error");
        }
    }
    else if (pid == 0)  //子进程:从键盘接收数据 -> 写到客户端
    {
        close(STDOUT_FILENO);
        char sendBuf[BUFSIZ] = {0};
        //从键盘接收数据
        while (true)
        {
            if (fgets(sendBuf,sizeof(sendBuf),stdin) == NULL)
            {
                break;
            }
            else if (strncmp(sendBuf,"quit",4) == 0)
            {
                close(peerSockfd);
                kill(getppid(),SIGUSR2);
                break;
            }
            //发送到client端
            if (write(peerSockfd,sendBuf,strlen(sendBuf)) == ssize_t(-1))
            {
                close(peerSockfd);
                err_exit("write socket error");
            }
            memset(sendBuf,0,sizeof(sendBuf));
        }
    }

    close(sockfd);
    return 0;
}

//client端完整代码与说明
#include "commen.h"

int main()
{
    signal(SIGUSR1,onSignal);

    //创建新的套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if (sockfd == -1)
    {
        err_exit("socket error");
    }

    //填写server端IP地址及其端口号
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8001);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //连接到server
    if (connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
    {
        err_exit("connect error");
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    //父进程:从键盘接收数据 -> 将之发送给server端
    if (pid > 0)
    {
        close(STDOUT_FILENO);
        char sendBuf[BUFSIZ] = {0};
        //从键盘接收数据
        while (true)
        {
            if (fgets(sendBuf,sizeof(sendBuf),stdin) == NULL)
            {
                break;
            }
            else if (strncmp(sendBuf,"quit",4) == 0)
            {
                close(sockfd);
                //发送SIGUSR1信号给子进程,将其关闭
                kill(pid,SIGUSR1);
                _exit(0);
            }
            //发送到server端
            if (write(sockfd,sendBuf,strlen(sendBuf)) == ssize_t(-1))
            {
                close(sockfd);
                err_exit("write socket error");
            }
            memset(sendBuf,0,sizeof(sendBuf));
        }
    }
    else if (pid == 0)  //子进程:从server端接收数据,将其打印到屏幕
    {
        close(STDIN_FILENO);

        char recvBuf[BUFSIZ] = {0};
        int readCount = 0;
        //从server端接收数据
        while ((readCount = read(sockfd,recvBuf,sizeof(recvBuf))) > 0)
        {
            recvBuf[readCount] = 0;
            //将其打印到屏幕
            fprintf(stdout,"%s",recvBuf);
            memset(recvBuf,0,sizeof(recvBuf));
        }
        if (readCount == 0)
        {
            close(sockfd);
            cout << "peer connect closed" << endl;

            //发送SIGUSR2信号给父进程,将其关闭
            kill(getppid(),SIGUSR2);
            _exit(0);
        }
        else if (readCount == -1)
        {
            close(sockfd);
            err_exit("read error");
        }
    }
    return 0;
}


可以看出,此时既没有端口占用,也没有僵尸进程!



-commen.h

#ifndef COMMEN_H_INCLUDED
#define COMMEN_H_INCLUDED

#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <iostream>
using namespace std;


void err_exit(std::string str)
{
    perror(str.c_str());
    exit(EXIT_FAILURE);
}

struct SendStruct
{
    int type;
    char text[BUFSIZ];
};

void onSignal(int signalNumber)
{
    switch (signalNumber)
    {
    case SIGUSR1:
        cout << "child receive SIGUSR1" << signalNumber << endl;
        _exit(0);
    case SIGUSR2:
        cout << "parent receive SIGUSR2: " << signalNumber << endl;
        _exit(0);
    default:
        cout << "RECV OTHRER SIGNAL" << endl;
    }
}

#endif // COMMEN_H_INCLUDED

附-Makefile

CC = g++ 
CPPFLAGS = -Wall -g -pthread

BIN = server client
SOURCES = $(BIN.=.cpp)

.PHONY: clean all 

all: $(BIN)

$(BIN): $(SOURCES)

clean:
    -rm -rf $(BIN) bin/ obj/ core

Socket 编程实践(5) --p2p聊天程序设计与实现