首页 > 代码库 > Linux Linux程序练习十二(select实现QQ群聊)

Linux Linux程序练习十二(select实现QQ群聊)

//头文件--helper.h#ifndef _vzhang#define _vzhang#ifdef __cplusplusextern "C"{#endif#define MAX_SOCKET_NUM 1024#define BUF_SIZE 1024//server create socketint server_socket(int port);//close socketint close_socket(int st);//start selectint start_select(int listen_st);//connect serverint connect_server(char * ipaddr, int port);//thread for recv messagevoid * thread_recv(void *arg);//thread for send messagevoid * thread_send(void *arg);#ifdef __cplusplus}#endif#endif
//辅助方法--helper.c#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>#include <time.h>#include <fcntl.h>#include <sys/select.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/socket.h>#include "helper.h"//create socketint create_socket(){    int st = socket(AF_INET, SOCK_STREAM, 0);    if (st < 0)    {        printf("create socket failed ! error message :%s\n", strerror(errno));        return -1;    }    return st;}//set reuseaddrint set_reuseaddr(int st){    int on = 1;    if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)    {        printf("reuseaddr failed ! error message :%s\n", strerror(errno));        return -1;    }    return 0;}//bind IP and portint bind_ip(int st, int port){    struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    //type    addr.sin_family = AF_INET;    //port    addr.sin_port = htons(port);    //address    addr.sin_addr.s_addr = htonl(INADDR_ANY);    if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) < 0)    {        printf("bind failed ! error message :%s\n", strerror(errno));        return -1;    }    return 0;}//listenint listen_port(int st, int num){    if (listen(st, num) < 0)    {        printf("listen failed ! error message :%s\n", strerror(errno));        return -1;    }    return 0;}//server create socketint server_socket(int port){    int st = create_socket();    if (st < 0)    {        return -1;    }    if (set_reuseaddr(st) < 0)    {        return -1;    }    if (bind_ip(st, port) < 0)    {        return -1;    }    if (listen_port(st, 20) < 0)    {        return -1;    }    return st;}//get IP address by sockaddr_invoid sockaddr_toa(const struct sockaddr_in *addr, char * 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]);}//accept clientint accept_client(int st){    if (st < 0)    {        printf("function accept_client param not correct!\n");        return -1;    }    struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    socklen_t len = sizeof(addr);    int client_st = accept(st, (struct sockaddr *) &addr, &len);    if (client_st < 0)    {        printf("accept failed ! error message :%s\n", strerror(errno));        return -1;    }    char buf[30] = { 0 };    sockaddr_toa(&addr, buf);    printf("accept by %s\n", buf);    return client_st;}//close socketint close_socket(int st){    if (st < 0)    {        printf("function close_socket param not correct!\n");        return -1;    }    close(st);    return 0;}//protect messageint protect_message(int st, int * starr){    if (st < 0 || starr == NULL)    {        printf("function recv_message param not correct!\n");        return -1;    }    char buf[BUF_SIZE] = { 0 };    int i = 0;    int rc = recv(st, buf, sizeof(buf), 0);    if (rc < 0)    {        printf("recv failed ! error message :%s\n", strerror(errno));        return -1;    } else if (rc == 0)    {        printf("client is closed !\n");        return -1;    }    /*     *QQ消息处理:接收到client1的消息直接发送给client2     */    for (; i < MAX_SOCKET_NUM; i++)    {        if (starr[i] != st && starr[i] != -1)        {            //向其他的chient发送消息            if (send(starr[i], buf, strlen(buf), 0) < 0)            {                printf("send failed ! error message :%s\n", strerror(errno));                return -1;            }        }    }    return 0;}//send messageint send_message(int st, int * starr){    return 0;}//start selectint start_select(int listen_st){    if (listen_st < 0)    {        printf("function create_select param not correct!\n");        return -1;    }    int i = 0;    //定义select第一个参数变量    int maxfd = 0;    int rc = 0;    //创建客户端socket池    int client[MAX_SOCKET_NUM] = { 0 };    //初始化socket池    for (; i < MAX_SOCKET_NUM; i++)    {        client[i] = -1;    }    //定义事件数组结构    fd_set allset;    while (1)    {        //清空事件数组        FD_ZERO(&allset);        //将服务器端socket加入事件数组        FD_SET(listen_st, &allset);        //假设值最大的socket是listen_st        maxfd = listen_st;        //将所有客户端socket加入到事件数组        for (i = 0; i < MAX_SOCKET_NUM; i++)        {            if (client[i] != -1)            {                FD_SET(client[i], &allset);                if (maxfd < client[i])                {                    maxfd = client[i];                }            }        }        //select阻塞接收消息        rc = select(maxfd + 1, &allset, NULL, NULL, NULL);        //select函数报错,直接退出循环        if (rc < 0)        {            printf("select failed ! error message :%s\n", strerror(errno));            break;        }        if (FD_ISSET(listen_st, &allset))        {            rc--;            //处理服务端socket事件            int client_st = accept_client(listen_st);            if (client_st < 0)            {                /*                 * 如果accept失败,直接退出select,                 * continue不太合适,因为其他的客户端事件还没有处理,这样就会丢事件                 */                break;            }            //将客户端socket放到socket池中            for (i = 0; i < MAX_SOCKET_NUM; i++)            {                if (client[i] == -1)                {                    client[i] = client_st;                    break;                }            }            if (i == MAX_SOCKET_NUM)            {                printf("服务器端socket池已经满了");                //socket池已满,关闭客户端连接                close_socket(client_st);            }        }        //处理客户端事件        for (i = 0; i < MAX_SOCKET_NUM; i++)        {            //如果事件数组中的事件已经处理完成,直接进入跳出当前循环            if (rc <= 0)            {                break;            }            if (client[i] != -1)            {                if (FD_ISSET(client[i], &allset))                {                    //该socket有事件发生                    if (protect_message(client[i], client) < 0)                    {                        //接收消息失败,但是由于逻辑复杂,直接退出select                        //关闭客户端socket                        close_socket(client[i]);                        //将该socket从socket池中删除                        client[i] = -1;                        break;                    }                }            }        }    }    return 0;}//connect serverint connect_server(char * ipaddr, int port){    int st = create_socket();    if (st < 0)    {        return -1;    }    struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    //type    addr.sin_family = AF_INET;    addr.sin_port = htons(port);    addr.sin_addr.s_addr = inet_addr(ipaddr);    if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) < 0)    {        printf("connect failed ! error message :%s\n", strerror(errno));        return -1;    }    return st;}//thread for recv messagevoid * thread_recv(void *arg){    int st = *(int *) arg;    if (st < 0)    {        printf("function thread_recv param not correct!\n");        return NULL;    }    char buf[BUF_SIZE] = { 0 };    while (1)    {        if (recv(st, buf, sizeof(buf), 0) < 0)        {            printf("recv failed ! error message :%s\n", strerror(errno));            break;        }        printf("%s", buf);        memset(buf, 0, sizeof(buf));    }    return NULL;}//thread for send messagevoid * thread_send(void *arg){    int st = *(int *) arg;    if (st < 0)    {        printf("function thread_send param not correct!\n");        return NULL;    }    char buf[BUF_SIZE] = { 0 };    while (1)    {        read(STDIN_FILENO, buf, sizeof(buf));        if (send(st, buf, sizeof(buf), 0) < 0)        {            printf("send failed ! error message :%s\n", strerror(errno));            break;        }        memset(buf, 0, sizeof(buf));    }    return NULL;}
//QQ客户端#include <stdio.h>#include <stdlib.h>#include <string.h>#include <pthread.h>#include <unistd.h>#include <errno.h>#include "helper.h"int main(int arg, char *args[]){    if (arg < 3)    {        printf("please print two param !\n");        return -1;    }    int port = atoi(args[2]);    char ipaddr[30] = { 0 };    strcpy(ipaddr, args[1]);    //connect server    int st = connect_server(ipaddr, port);    if (st < 0)    {        return -1;    }    //recv thread    pthread_t thr1, thr2;    if (pthread_create(&thr1, NULL, thread_recv, &st) != 0)    {        printf("pthread_create failed ! error message :%s\n", strerror(errno));        return -1;    }    if (pthread_create(&thr2, NULL, thread_send, &st) != 0)    {        printf("pthread_create failed ! error message :%s\n", strerror(errno));        return -1;    }    //join    pthread_join(thr1,NULL);    pthread_join(thr2,NULL);    //close socket    close_socket(st);    return 0;}
//QQ服务端#include <stdio.h>#include <stdlib.h>#include <string.h>#include "helper.h"int main(int arg,char *args[]){    //服务器端需要传入端口号    if(arg<2)    {        printf("please print one param !\n");        return -1;    }    int port=atoi(args[1]);    int st=server_socket(port);    if(st<0)    {        return -1;    }    //开启select 监听事件    start_select(st);    //close socket    close_socket(st);    return 0;}
.SUFFIXES:.c .oCC=gccSRCS1=QQserver.c    helper.cSRCS2=QQclient.c    helper.cOBJS1=$(SRCS1:.c=.o)OBJS2=$(SRCS2:.c=.o)EXEC1=mserverEXEC2=mclientstart:$(OBJS1) $(OBJS2)    $(CC) -o $(EXEC1) $(OBJS1)    $(CC) -o $(EXEC2) $(OBJS2) -lpthread    @echo "-------ok-----------".c.o:    $(CC) -Wall -g -o $@ -c $<clean:    rm -f $(OBJS1)    rm -f $(EXEC1)    rm -f $(OBJS2)    rm -f $(EXEC2)
小结:
  这次程序编码调试都很快,调试中第一个错误,客户端连接不上服务器,但是connect不报错,我几乎每次写网络程序都有这个问题,就目前而言有两种出错可能
出错场景1:服务端socket有问题,为0或者不是正确的socket
出错场景2:服务器端的socket没有放在select池中,没有被select监视
我暂时就只有这两种犯错场景。
调试第二个错误:“Segmentation fault”,这个错误我觉得一般都是操作内存出了问题,内存泄漏,我这边是因为在函数sockaddr_toa()中
unsigned char *p=(unsiged char *)&(addr->sin_addr.s_addr); //没有取addr->sin_addr.s_addr的地址转化成unsigned char类型,发生内存泄漏
调试第三个错误是逻辑错误,每次有客户端socket接收数据出错后,我没有从socket池中将该socket清除,导致select()函数不再阻塞,每次直接返回

 

Linux Linux程序练习十二(select实现QQ群聊)