我该如何使用C套接字正确发送数据和管理进程

时间:2019-07-15 13:04:31

标签: c sockets

我正在尝试使用Socket建立一个简单的聊天室,但是有一个我无法解决的问题。套接字服务器可以接收任何客户端的数据,并且服务器还可以将数据发送回客户端。我的问题是,当我创建多个客户端时,新创建的客户端可以将数据发送到服务器,并且服务器可以按预期将数据传递到现有客户端,但是如果现有客户端将数据发送到服务器,而服务器将数据发送到客户端,则无法正常工作新创建的客户端。错误是错误的文件描述符。有人可以帮我吗,Thx。

#define MSGSIZE    2048
#define CLIENTSIZE 2

typedef struct {
    long _type;
    char _data[MSGSIZE];
} msgQueue;

typedef struct {
    int shmid;
    int writablePid;
    int childProcessSize;
    int clientSockfds[CLIENTSIZE];
} SockfdInShm;

void serverRun(void);
void childProcess(int sockfd, struct sockaddr_in addr);
void *threadDoSend(void *arg);
void *threadDoRecv(void *arg);
int msgQueueGet(void);
void updateShm(SockfdInShm *shmLinker, int pos);
SockfdInShm* sockfdInShmGet(void);

int main(void)
{
    serverRun();
    return 0;
}

void serverRun(void)
{
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int sockfd, _sockfd, addrlen, size;
    addrlen = sizeof(struct sockaddr); 

    msgQueue msg = {0};
    int msgid = msgQueueGet(); 

    //create socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd) {
        perror("Fail to create socket");
        exit(-1);
    }

    printf("The server is running...\n");
    //enable address reuse
    int on = 1;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
        perror("setsockopt error");
        exit(-1);
    }

    //bind the socket with an IP
    memset(&server_addr, 0, addrlen);
    server_addr.sin_family      = AF_INET;
    server_addr.sin_port        = htons(1234);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if(-1 == bind(sockfd, (struct sockaddr*)&server_addr, addrlen)){
        perror("bind error");
        exit(-1);
    }

    //listen the socket
    if(-1 == listen(sockfd, 20)){
        perror("listen error");
        exit(-1);
    } 

    int pos;
    char serverInput[128];
    pid_t pidChild;
    SockfdInShm *shmLinker = sockfdInShmGet();
    shmLinker->childProcessSize = 0;
    memset(&(shmLinker->clientSockfds), 0, sizeof(int)*CLIENTSIZE);
    while(1) {
        addrlen = sizeof(struct sockaddr); 
        _sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen);

        pos = shmLinker->childProcessSize;
        shmLinker->clientSockfds[shmLinker->childProcessSize++] = _sockfd;

        pidChild = fork();
        if(pidChild < 0) {
            perror("fork error");
            close(_sockfd);
            continue;
        } else if(pidChild > 0) {
            //close(_sockfd);
            //fgets(serverInput, 128, stdin);
            //if(strcmp(serverInput, ".exit\n") == 0) break;
            continue;
        } else {
            printf("fork success, child pid: %d\n", getpid());
            close(sockfd);
            childProcess(_sockfd, client_addr);
            close(_sockfd);
            updateShm(shmLinker, pos);
            exit(0);
        }
    }

    close(sockfd);
    shmctl(shmLinker->shmid, IPC_RMID, 0); 
    printf("This server has been closed\n");
}

void childProcess(int sockfd, struct sockaddr_in addr)
{
    printf("IP:   %s\n", inet_ntoa(addr.sin_addr));
    printf("Port: %d\n", ntohs(addr.sin_port));

    pthread_t sendptid, recvptid;
    pthread_create(&sendptid, NULL, threadDoSend, &sockfd);
    pthread_create(&recvptid, NULL, threadDoRecv, &sockfd); 

    pthread_join(sendptid, NULL);
    pthread_join(recvptid, NULL);
}

void *threadDoSend(void *arg)
{
    msgQueue msg = {0};
    SockfdInShm *shmLinker = sockfdInShmGet();
    int sockfd = *(int *)arg;
    //create a unique key for msg queue
    int msgid = msgQueueGet();

    int j, r;
    while(1) {
        msg._type = sockfd; 
        memset(&msg._data, 0, MSGSIZE);
        msgrcv(msgid, &msg, MSGSIZE, msg._type, 0);
        if(strcmp(msg._data, ".exit\n") == 0) break;

        for(j=0; j<CLIENTSIZE; ++j) {
            if(shmLinker->clientSockfds[j] == 0 || shmLinker->clientSockfds[j] == sockfd) continue;
           // if(shmLinker->clientSockfds[j] == 0) continue;
            printf("send to sockfd %d\n", shmLinker->clientSockfds[j]);
            r = send(shmLinker->clientSockfds[j], msg._data, strlen(msg._data), 0);
            if(r==-1) perror("send");**//here the error occurs**
        }
    }
}

void *threadDoRecv(void *arg)
{
    msgQueue msg = {0};
    int sockfd = *(int *)arg;
    int msgid, flag;

    //create a unique key for msg queue
    msgid = msgQueueGet();   
    while(1) {
        memset(&msg._data, 0, MSGSIZE);
        recv(sockfd, msg._data, MSGSIZE, 0);
        msg._type = sockfd;
        flag = msgsnd(msgid, &msg, strlen(msg._data), 0);
        if(flag==-1) perror("msgsnd in threadDoRecv");

        if(strcmp(msg._data, ".exit\n") == 0) break;
        printf("%s\n", msg._data);
    }
}

int msgQueueGet(void)
{
    int mkey = ftok("/dev", 'a');
    if(mkey == -1) {
        perror("ftok");
        exit(-1);
    }

    int msgid = msgget(mkey, IPC_CREAT|0666);
    if(msgid == -1) {
        perror("msgget");
        exit(-1);
    }
    return msgid;
}

void updateShm(SockfdInShm *shmLinker, int pos)
{
    shmLinker->clientSockfds[pos] = 0;
    shmLinker->childProcessSize--;
    int i, j;
    for(i=0; i<CLIENTSIZE; ++i) {
        if(shmLinker->clientSockfds[i]) continue;
        for(j=i+1; j<CLIENTSIZE; ++j) {
           if(!shmLinker->clientSockfds[j]) continue;
           shmLinker->clientSockfds[i] = shmLinker->clientSockfds[j];
           shmLinker->clientSockfds[j] = 0;
           break;
        }
    }
}

SockfdInShm* sockfdInShmGet(void)
{
    int shmid;
    SockfdInShm *shmLinker = NULL;

    shmid = shmget((key_t)6638, sizeof(SockfdInShm), IPC_CREAT|0666);
    if(shmid == -1) {
        perror("shmid");
        exit(EXIT_FAILURE);
    }
    shmLinker = (SockfdInShm *)shmat(shmid, 0, 0);
    shmLinker->shmid = shmid;

    return shmLinker;
}

2 个答案:

答案 0 :(得分:0)

文件描述符编号(例如表示打开的套接字的编号)本质上没有意义。您不能只是将这样的数字从一个进程转移到另一个进程,并期望使用它来访问同一文件(在这种情况下为套接字)。进程从其父进程继承了开放文件描述符,这就是为什么分配给新客户端处理的进程可以将消息转发给较旧的客户端的原因,但是这些进程的较早兄弟姐妹不知道较年轻的兄弟姐妹正在服务的套接字。在较旧的客户端中,较新进程的文件描述符编号实际上不是有效的文件描述符。

一种通过UNIX域套接字将文件描述符从一个进程复制到另一个进程的方法,但是最好重新设计应用程序,以便每个子进程仅操纵一个客户端套接字。您可以让那些进程为其他人排队消息(尽管某些细节可能有些棘手),但是您不应该让它们直接写给彼此的客户。

此外,您应该确保派生一个子进程来处理该套接字后,父进程关闭每个客户端套接字的副本,否则您将在父级和许多客户端中打开一堆未使用的套接字。这构成了令人讨厌的文件描述符泄漏,尽管您可能不会在简短的狭窄测试中注意到这种影响。

答案 1 :(得分:-1)

代替:

pthread_create(&sendptid, NULL, threadDoSend, &sockfd);

您应该按值传递:

pthread_create(&sendptid, NULL, threadDoSend, sockfd);

在转换为(int)的线程内-

int sockfd = (int)arg;

当通过引用将变量传递给线程时,它可能会更改,因为主线程可能会再次调用它并具有不同的值。