Linxu网络编程——myp2p-qq

功能描述

本博文实现一个类似qq的聊天功能,两个client端连接上服务器,不必知道对方的ip地址,就可以互相发送消息。

预备知识

**1.Linxu网络编程
2.Linux多线程同步技术**

/*
 * pub.h
 *
 *  Created on: 2016年10月30日
 *      Author: Allen
 */

#ifndef PUB_H_
#define PUB_H_
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define  BUFSIZE 32768

int socket_create(int port);
void socket_accept(int listen_st);
void init_socket_client();
void deliver(int index,const char *buf,int len);
void socket_work(int index);
void *socket_contrl(void *arg);


#endif /* PUB_H_ */
/*
 * pub.c
 *
 *  Created on: 20161030日
 *      Author: Allen
 */

#include"pub.h"

int socket_client[2]; //申明2个socket_client,设为全局变量,管理2个client的socket连接

void init_socket_client() //初始化int socket_client[2]数组
{
    memset(socket_client, 0, sizeof(socket_client));
}

//接收到来自client发送的消息,index0,代表接收到socket_client[0]消息,然后给socket_client[1]发送消息
//index1,代表接收到socket_client[1]消息,然后给socket_client[0]发送消息
//buf为发送消息内容,len为发送消息长度
void deliver(int index, const char *buf, int len)
{
    int rc = 0;
    if (index == 0) //如果 index0,代表01发消息
    {
        if (socket_client[1] == 0) //1不在线
        {
            printf("%d : user not online\n", index);
        } else
        {
            rc = send(socket_client[1], buf, len, 0); //将消息下发给1
            if (rc <= 0)
            {
                if (rc < 0)
                    printf("send faied %s\n", strerror(errno));
                else
                    printf("socket disconnect %s\n", strerror(errno));
            }
        }
    }
    if (index == 1)
    {
        if (socket_client[0] == 0)
        {
            printf("%d : user not online\n", index);
        } else
        {
            rc = send(socket_client[0], buf, len, 0);
            if (rc <= 0)
            {
                if (rc < 0)
                    printf("send faied %s\n", strerror(errno));
                else
                    printf("socket disconnect %s\n", strerror(errno));
            }
        }
    }
}

//接收来自client端的连接,index0代表接收来自socket_client[0]的消息
//index1代表接收来自socket_client[1]的消息
void socket_work(int index)
{
    char buf[BUFSIZE];
    int rc = 0;
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        rc = recv(socket_client[index], buf, BUFSIZE, 0);
        if (rc <= 0) //client连接断开
        {
            if (rc < 0)
                printf("%d:recv failed %s\n", index, strerror(errno));
            else
                printf("%d:socket disconnect\n", index);
            close(socket_client[index]);
            socket_client[index] = 0; //如果client的socket已经断开,那么置socket_client[]数组中相应的元素为0
            break;
        } else
        {
            printf("%d:recv is '%s'\nrecv %d bytes\n", index, buf, rc);
            //index0,代表接收到socket_client[0]消息,然后给socket_client[1]发送消息
            //index1,代表接收到socket_client[1]消息,然后给socket_client[0]发送消息
            //buf为发送消息内容
            deliver(index, buf, rc);
        }
    }
}

void *socket_contrl(void *arg)          //server端的线程入口函数
{
    int client_st = *(int *) arg;           //得到从accept函数返回的来自client端的socket描述符
    free((int*) arg);
    printf("control thread begin\n");
    int index = 0;
    if (socket_client[0] == 0)//如果socket_client[0]空闲,就将来自client端的socket赋给socket_client[0]
    {
        socket_client[0] = client_st;
    } else          //如果socket_client[1]空闲,就将来自client端的socket赋给socket_client[1]
    {
        if (socket_client[1] == 0)
        {
            socket_client[1] = client_st;
            index = 1;
        } else          //socket_client[0]socket_client[1]都不空闲
        {
            printf("connect full\n");
            close(client_st);       //socket_client[2]两个成员都已经在线了,拒绝其他client连接
            return NULL;
        }
    }
    //接收来自client端的连接,index0代表接收来自socket_client[0]的消息
    //index1代表接收来自socket_client[1]的消息
    socket_work(index);
    printf("control thread end\n");
    return NULL;
}

int socket_create(int port) //创建server端socket在port指定端口listen
{
    int st = socket(AF_INET, SOCK_STREAM, 0);   //创建TCP socket描述符
    if (st == -1)
    {
        printf("socket failed %s\n", strerror(errno));
        return -1;
    }
    int on = 1;
    if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        printf("setsockopt failed %s\n", strerror(errno));
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if ((bind(st, (struct sockaddr*) &addr, sizeof(addr)) == -1))   //在port端口上绑定
    {
        printf("bind failed %s\n", strerror(errno));
        return -1;
    }
    if (listen(st, 20) == -1)   //开始listen
    {
        printf("listen failed %s\n", strerror(errno));
        return -1;
    }
    return st;  //返回listen的server端socket描述符
}

void socketaddr_toa(const struct sockaddr_in*addr, char *IPAddr)//将addr转化为字符串IPAddr
{
    unsigned char *p = (unsigned char*) &(addr->sin_addr.s_addr);
    sprintf(IPAddr, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
}

void socket_accept(int listen_st)   //server端socketaccept函数
{
    pthread_t thd;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程属性为可分离

    struct sockaddr_in cliaddr;
    while (1)
    {
        memset(&cliaddr, 0, sizeof(cliaddr));
        socklen_t len = sizeof(cliaddr);
        int client_st = accept(listen_st, (struct sockaddr*) &cliaddr, &len); //accept函数阻塞,直到有client的socket连接到达
        if (client_st == -1)
        {
            printf("accept failed %s\n", strerror(errno));
            break;
        } else
        {
            char sIP[32];
            memset(sIP, 0, sizeof(sIP));
            socketaddr_toa(&cliaddr, sIP);
            printf("accept by %s\n", sIP);
            int *tmp = malloc(sizeof(int));
            *tmp = client_st;
            //只要accept到来自client端的socket,就启动一个线程,线程入口函数为socket_contrl
            pthread_create(&thd, &attr, socket_contrl, tmp);
        }
    }
    pthread_attr_destroy(&attr);

}

/*
 * qqclient.c
 *
 *  Created on: 2016年10月30日
 *      Author: Allen
 */

#include"pub.h"


int socket_connect(const char*hostname,int port)client端socket连接到hostname指定的IP地址和port端口号
{
    int client_st=socket(AF_INET,SOCK_STREAM,0);//建立一个TCP socket描述符
    if(client_st==-1)
    {
        printf("client_st socket failed %s\n",strerror(errno));
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(port);
    addr.sin_addr.s_addr=inet_addr(hostname);

    if((connect(client_st,(struct sockaddr*)&addr,sizeof(addr)))==-1)//连接到指定IP
    {
        printf("connect failed %s\n",strerror(errno));
        return -1;
    }else
    {
        printf("connect to %s:%d success\n",hostname,port);
    }
    return client_st;//连接成功后返回socket描述符
}

void *socket_read(void *arg)//读socket数据的线程入口函数
{
    int st=*(int *)arg;
    char buf[BUFSIZE];
    while(1)
    {
        memset(buf,0,BUFSIZE);
        int rc=recv(st,buf,BUFSIZE,0);//循环从socket中读取数据
        if(rc<=0)//如果recv失败,代表TCP连接中断,循环break
        {
            printf("recv failed %s\n",strerror(errno));
            break;
        }else//将从socket读取到 的数据打印到屏幕
        {
            printf("recv '%s'\nrecv %d bytes\n",buf,rc);
        }

    }
    return NULL;
}

void *socket_write(void *arg)//写socket数据的线程入口函数
{
    int st=*(int*)arg;
    char buf[BUFSIZE];
    while(1)
    {
        memset(buf,0,BUFSIZE);
        read(STDIN_FILENO,buf,BUFSIZE);//从键盘读取数据,放入buf中
        int ilen=strlen(buf);
        if(buf[ilen-1]=='\n')//如果读到的数据最后为回车键,那么把回车键替换为空字符'\0'
        {
            buf[ilen-1]=0;
        }
        int rc=send(st,buf,strlen(buf),0);//将从键盘读取到的字符发送出去
        printf("send '%s'\nsend %d bytes\n",buf,rc);
        if(rc<=0)
        {
            printf("send failed %s\n",strerror(errno));
            break;
        }
    }
    return NULL;
}


int main(int arg,char *args[])
{
    if(arg<3)
    {
        printf("usage:qqclient hostname port\n");
        return 0;
    }
    int port=atoi(args[2]);
    if(port==0)
    {
        printf("port is invalid\n");
        return 0;
    }
    printf("qqclient begin\n");
    int st=socket_connect(args[1],port);//第一个参数为IP地址,连接到第一个参数指定的IP地址
    if(st==-1)//连接失败,main函数退出
    {
        printf("socket connect failed\n");
        return 0;
    }
    pthread_t thr_read,thr_write;
    pthread_create(&thr_read,0,socket_read,&st);//启动读socket数据线程
    pthread_create(&thr_write,0,socket_write,&st);//启动写socket数据线程
    pthread_join(thr_read,NULL);
    //pthread_join(thr_write, NULL);//如果等待thr_write退出,main函数可能被挂起
    close(st);
    printf("qqclient end\n");
    return 0;
}
/*
 ============================================================================
 Name        : qqserver.c
 Author      : Allen
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include"pub.h"

int main(int arg,char*args[])
{
    if(arg<2)
    {
        printf("usage:qqserver port\n");
        return 0;
    }
    int port=atoi(args[1]);
    if(port==0)
    {
        printf("port is invalid\n");
        return 0;
    }
    printf("qqserver begin\n");
    init_socket_client();//初始化int socket_client[2]数组
    int listen_st=socket_create(port);//建立server端socket,在port指定的端口号上listen
    if(listen_st==-1)//如果建立失败,main函数退出
    {
        printf("socket create failed\n");
        return EXIT_FAILURE;
    }
    socket_accept(listen_st);//serversocket开始accept
    close(listen_st);
    printf("qqserver end\n");
    return 0;
}

运行结果

server:
Linxu网络编程——myp2p-qq_第1张图片
client1:
Linxu网络编程——myp2p-qq_第2张图片
client2:
Linxu网络编程——myp2p-qq_第3张图片

你可能感兴趣的:(Linux网络编程,网络编程,p2p-qq,聊天,多线程)