Linux C语言 31-网络编程之TCP例程

Linux C语言 31-网络编程之TCP例程

本节关键字:C语言 网络编程 套接字操作 TCP协议 服务端 客户端 非阻塞
相关C库函数:setsockopt, socket, bind, listen, accept, recv, send, close, select, connect

相关接口介绍

Linux C语言 30-套接字操作

例程执行任务说明

本例程中服务端的任务:

  • 等待新的客户端连接
  • 读取已连接客户端发来的消息并回复
  • 断开并移除连接异常的客户端

本例程中客户端的任务:

  • 创建5个客户端,并使每个客户端都成功连接服务端
  • 间隔1秒,发送消息告知服务端自己的套接字
  • 通信次数达到10次时,断开当前连接
  • 处于未连接状态时自动进行服务端重连

TCP协议服务端例程实现

// tpcserver.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_CLIENT_CNT    10

typedef struct
{
    int socket;
    int state;
    int cnt;
} TcpClient;

typedef struct
{
    TcpClient	s[10];        
    int    		count;
} SocketArray;


void msleep(int msecs);
int  addSocket(SocketArray *array, int socket);
int  delSocket(SocketArray *array, int socket);
int  getOneServerSocket(const char *ip, int port);


int main(int argc, char *argv[])
{
    int rc, rxn, txn, srvfd, port;
    char ip[32] = {0};
    
    int i, maxfd;
    fd_set readfds;
    SocketArray sArray;
    bzero(&sArray, sizeof(sArray));
    
    char rxBuffer[1024] = {0};
    char txBuffer[1024] = {0};
    
    if (argc != 3)
    {
        printf("please input correct arguments:\n");
        printf("\ttcpserver ip port\n\n");
        return -1;
    }
    
    // 解析ip和port
    strcpy(ip, argv[1]);
    port = atoi(argv[2]);
    printf("ip: %s, port: %d\n", ip, port);
    
    // 创建套接字
    srvfd = getOneServerSocket(ip, port);
    
    // 循环非阻塞读取套接字上的数据
    while (1)
    {
        maxfd = 0;
        FD_ZERO(&readfds);
        
        // 将服务端套接字加入select队列
        FD_SET(srvfd, &readfds);
        if (srvfd > maxfd)
            maxfd = srvfd;
        
        // 将所有已连接客户端的套接字加入select队列
        for (i=0; i<sArray.count; i++)
        {
            FD_SET(sArray.s[i].socket, &readfds);
            if (sArray.s[i].socket > maxfd)
                maxfd = sArray.s[i].socket;
        }
        
        // 非阻塞等待套接字传来数据
        struct timeval timeout = {2, 0}; // 2s
        if (select(maxfd+1, &readfds, NULL, NULL, &timeout) <= 0)
        {
            msleep(5);
            continue;
        }
        
        // 先确认监听套接字
        if (FD_ISSET(srvfd, &readfds))
        {
            int newfd, addrlen;
            struct sockaddr_in naddr;
            
            addrlen = sizeof(naddr);
            newfd = accept(srvfd, (struct sockaddr*)&naddr, &addrlen);
            if (newfd == -1)
            {
                perror("accept");
            }
            else
            {
                printf("add new client[%s:%d], socket: %d\n", inet_ntoa(naddr.sin_addr), ntohs(naddr.sin_port), newfd);
                addSocket(&sArray, newfd);
            }
        }
        
        // 再确认客户端套接字
        for (i=0; i<sArray.count; i++)
        {
            if (FD_ISSET(sArray.s[i].socket, &readfds))
            {
                bzero(rxBuffer, sizeof(rxBuffer));
                rxn = recv(sArray.s[i].socket, rxBuffer, sizeof(rxBuffer)-1, 0);
                if (rxn <= 0)
                {
                    printf("socket exception, socket: %d\n", sArray.s[i].socket);
                    delSocket(&sArray, sArray.s[i].socket);
                    continue;
                }
                
                printf("client[%d]: %s\n", sArray.s[i].socket, rxBuffer);
                
                bzero(txBuffer, sizeof(txBuffer));
                sprintf(txBuffer, "server reply client[%d]: %s", sArray.s[i].socket, rxBuffer);
                txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer), 0);
                if (txn <= 0)
                {
                    printf("socket exception, socket: %d\n", sArray.s[i].socket);
                    delSocket(&sArray, sArray.s[i].socket);
                    continue;
                }
            }
        }
    }
    
    for (i=0; i<sArray.count; i++)
        close(sArray.s[i].socket);
    close(srvfd);
    return 0;
}

void msleep(int msecs)
{
    struct timeval stoptime;
    stoptime.tv_sec  = (int)(msecs/1000);
    stoptime.tv_usec = (int)(msecs%1000)*1000;
    select(0, 0, 0, 0, &stoptime);
}

int  addSocket(SocketArray *array, int socket)
{
    if (array->count >= MAX_CLIENT_CNT)
    {
        printf("The number of clients has reached the maximum limit(%d)\n", MAX_CLIENT_CNT);
        return -1;
    }
    
    int i, exists=0;
    for (i=0; i<array->count; i++)
    {
        if (array->s[i].socket == socket)
        {
            exists = 1;
            break;
        }
    }

    if (exists == 1)
    {
        printf("client exists, socket: %d, index: %d\n", array->s[i].socket, i);
        return socket;
    }
    
    array->s[array->count].socket = socket;
    array->count += 1;
    
    printf("add client[%d], client count: %d\n", array->s[array->count].socket, array->count);
    return socket;
}

int  delSocket(SocketArray *array, int socket)
{
    if (array->count <= 0)
    {
        printf("No client\n");
        return -1;
    }
    
    int i;
    SocketArray tmpArr;
    bzero(&tmpArr, sizeof(tmpArr));
    
    for (i=0; i<array->count; i++)
    {
        if (array->s[i].socket == socket)
        {
            printf("remove client, socket: %d\n", socket);
            close(socket);
            continue;
        }
        
        tmpArr.s[tmpArr.count].socket = array->s[i].socket;
        tmpArr.count += 1;
    }
    
    memset(array, 0, sizeof(SocketArray));
    memcpy(array, &tmpArr, sizeof(tmpArr));
    return socket;
}

int getOneServerSocket(const char *ip, int port)
{
    int rc, srvfd, reused, timeout;
    struct sockaddr_in addr;
    
    srvfd = socket(AF_INET, SOCK_STREAM, 0);
    if (srvfd == -1)
    {
        perror("create socket");
        exit(-1);
    }
    
    // 设置端口复用
    reused = 1;
    rc = setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &reused, sizeof(reused));
    if (rc == -1)
    {
        perror("SO_REUSEDADDR");
        goto EXIT;
    }
    // 设置发送超时限制
    timeout = 1000; // 1秒
    setsockopt(srvfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
    if (rc == -1)
    {
        perror("SO_SNDTIMEO");
        goto EXIT;
    }
    
    // 套接字绑定本地的ip和port
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    bzero(addr.sin_zero, sizeof(addr.sin_zero));
    
    rc = bind(srvfd, (struct sockaddr*)&addr, sizeof(addr));
    if (rc == -1)
    {
        perror("bind");
        goto EXIT;
    }
    
    // 监听套接字
    rc = listen(srvfd, 10);
    if (rc == -1)
    {
        perror("listen");
        goto EXIT;
    }
    
    return srvfd;
    
EXIT:
    close(srvfd);
    exit(-1);
}

###TCP协议客户端例程

// tcpclient.c
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_CLIENT_CNT    5
#define OFFLINE            0
#define ONLINE            1
#define MAX_MSG_SEND    10

typedef struct
{
    int socket;
    int state;
    int cnt;
} TcpClient;

typedef struct
{
    TcpClient     s[10];
    int         count;
} SocketArray;


void msleep(int msecs);
int  addSocket(SocketArray *array, int socket);
int  delSocket(SocketArray *array, int socket);
int  getOneClientSocket(const char *ip, int port);
void setClientState(SocketArray *array, int fd, int state);
int  connectToServer(int fd, const char *ip, int port);


int main(int argc, char *argv[])
{
    int rc, clifd, rxn, txn, port;
    char ip[32] = {0};
    
    int i, j, maxfd;
    fd_set readfds;
    SocketArray sArray;
    
    char rxBuffer[1024] = {0};
    char txBuffer[1024] = {0};
    
    
    if (argc != 3)
    {
        printf("please input correct arguments:\n");
        printf("\ttcpclient ip port\n\n");
        return -1;
    }
    
    // 解析ip和port
    strcpy(ip, argv[1]);
    port = atoi(argv[2]);
    printf("ip: %s, port: %d\n", ip, port);
    
    while (1)
    {
        bzero(&sArray, sizeof(sArray));
        
        // 创建 MAX_CLIENT_CNT 个客户端
        for (i=0; i<MAX_CLIENT_CNT; i++)
        {
            clifd = getOneClientSocket(ip, port);
            if (clifd == -1)
                continue;
            
            printf("create client, socket: %d\n", clifd);
            addSocket(&sArray, clifd);
        }
        
        // 所有离线客户端连接服务端
        for (i=0; i<sArray.count; i++)
        {
            if (sArray.s[i].state == OFFLINE)
            {
                rc = connectToServer(sArray.s[i].socket, ip, port);
                if (rc == 0)
                    setClientState(&sArray, sArray.s[i].socket, ONLINE);
                else
                    setClientState(&sArray, sArray.s[i].socket, OFFLINE);
            }
        }
        
        // 所有在线客户端主动向服务端打招呼
        for (i=0; i<sArray.count; i++)
        {
            if (sArray.s[i].state == ONLINE)
            {
                bzero(txBuffer, sizeof(txBuffer));
                sprintf(txBuffer, "Hello, i'm client[%d], i will disconnect after sending data %d times", sArray.s[i].socket, MAX_MSG_SEND-sArray.s[i].cnt);
                
                txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer), 0);
                if (txn <= 0)
                {
                    printf("socket exception, socket: %d\n", sArray.s[i].socket);
                    setClientState(&sArray, sArray.s[i].socket, OFFLINE);
                    continue;
                }
                sArray.s[i].cnt += 1;
            }
            msleep(100 * i);
        }
        
        // 连接成功的客户端循环执行任务
        while (1)
        {
            j = 0;
            for (i=0; i<sArray.count; i++)
            {
                if (sArray.s[i].state == OFFLINE)
                    close(sArray.s[i].socket);
                else
                    j++;
            }
            sArray.count = j;
            
            if (sArray.count <= 0)
            {
                printf("No connected clients\n");
                break;
            }
            
            maxfd = 0;
            FD_ZERO(&readfds);
            
            // 将所有已连接客户端的套接字加入select队列
            for (i=0; i<sArray.count; i++)
            {
                if (sArray.s[i].state == OFFLINE)
                    continue;
                
                FD_SET(sArray.s[i].socket, &readfds);
                if (sArray.s[i].socket > maxfd)
                    maxfd = sArray.s[i].socket;
            }
            
            // 非阻塞等待套接字传来数据
            struct timeval timeout = {2, 0}; // 2s
            if (select(maxfd+1, &readfds, NULL, NULL, &timeout) <= 0)
            {
                msleep(5);
                continue;
            }
            
            // 处理已连接套接字上的消息及数据
            for (i=0; i<sArray.count; i++)
            {
                if (sArray.s[i].state == OFFLINE)
                    continue;
                
                if (FD_ISSET(sArray.s[i].socket, &readfds))
                {
                    // 发送消息数量达到 MAX_MSG_SEND 的客户端主动断开连接
                    if (sArray.s[i].cnt >= MAX_MSG_SEND)
                    {
                        printf("i'm client[%d], i will disconnect now\n", sArray.s[i].socket);
                        setClientState(&sArray, sArray.s[i].socket, OFFLINE);
                        continue;
                    }
                    
                    bzero(rxBuffer, sizeof(rxBuffer));
                    rxn = recv(sArray.s[i].socket, rxBuffer, sizeof(rxBuffer)-1, 0);
                    if (rxn <= 0)
                    {
                        printf("socket exception, socket: %d\n", sArray.s[i].socket);
                        setClientState(&sArray, sArray.s[i].socket, OFFLINE);
                        continue;
                    }
                    printf("client[%d] recv: %s\n", sArray.s[i].socket, rxBuffer);
                    
                    bzero(txBuffer, sizeof(txBuffer));
                    sprintf(txBuffer, "Hello, i'm client[%d], i will disconnect after sending data %d times", sArray.s[i].socket, MAX_MSG_SEND-sArray.s[i].cnt);
                    txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer)+1, 0);
                    if (txn <= 0)
                    {
                        printf("socket exception, socket: %d\n", sArray.s[i].socket);
                        setClientState(&sArray, sArray.s[i].socket, OFFLINE);
                        continue;
                    }
                    sArray.s[i].cnt += 1;
                }
                msleep(100 * i);
            }
            
            msleep(1000);
        }
        
        msleep(1000);
    }
    
    for (i=0; i<sArray.count; i++)
    {
        close(sArray.s[i].socket);
    }
    
    return 0;
}


void msleep(int msecs)
{
    struct timeval stoptime;
    stoptime.tv_sec  = (int)(msecs/1000);
    stoptime.tv_usec = (int)(msecs%1000)*1000;
    select(0, 0, 0, 0, &stoptime);
}

int  addSocket(SocketArray *array, int socket)
{
    if (array->count >= MAX_CLIENT_CNT)
    {
        printf("The number of clients has reached the maximum limit(%d)\n", MAX_CLIENT_CNT);
        return -1;
    }
    
    int i, exists=0;
    for (i=0; i<array->count; i++)
    {
        if (array->s[i].socket == socket)
        {
            exists = 1;
            break;
        }
    }
    
    if (exists == 1)
    {
        printf("client exists, socket: %d, index: %d\n", socket, i);
        return socket;
    }
    
    array->s[array->count].socket = socket;
    array->s[array->count].state = OFFLINE;
    array->count += 1;
    
    printf("add client[%d], client count: %d\n", array->s[i].socket, array->count);
    return socket;
}

int  delSocket(SocketArray *array, int socket)
{
    if (array->count <= 0)
    {
        printf("No client\n");
        return -1;
    }
    
    int i;
    SocketArray tmpArr;
    bzero(&tmpArr, sizeof(tmpArr));
    
    for (i=0; i<array->count; i++)
    {
        if (array->s[i].socket == socket)
        {
            printf("remove client, socket: %d\n", socket);
            close(socket);
            continue;
        }
        
        tmpArr.s[tmpArr.count].socket = array->s[i].socket;
        tmpArr.s[tmpArr.count].state = array->s[i].state;
        tmpArr.count += 1;
    }
    
    memset(array, 0, sizeof(SocketArray));
    memcpy(array, &tmpArr, sizeof(tmpArr));
    return socket;
}

int  getOneClientSocket(const char *ip, int port)
{
    int rc, clifd, reused, timeout;
    
    clifd = socket(AF_INET, SOCK_STREAM, 0);
    if (clifd == -1)
    {
        perror("create socket");
        return -1;
    }
    
    // 设置端口复用
    reused = 1;
    rc = setsockopt(clifd, SOL_SOCKET, SO_REUSEADDR, &reused, sizeof(reused));
    if (rc == -1)
    {
        perror("SO_REUSEDADDR");
        goto EXIT;
    }
    // 设置发送超时限制
    timeout = 1000; // 1秒
    setsockopt(clifd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
    if (rc == -1)
    {
        perror("SO_SNDTIMEO");
        goto EXIT;
    }
    
    return clifd;
    
EXIT:
    close(clifd);
    return -1;
}

int  connectToServer(int fd, const char *ip, int port)
{
    int rc;
    struct sockaddr_in addr;
    
    // 套接字绑定服务端的ip和port
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    bzero(addr.sin_zero, sizeof(addr.sin_zero));
    
    // 连接服务端
    rc = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (rc == -1)
    {
        printf("failed to connect server[%s:%d], socket: %d\n", ip, port, fd);
        return -1;
    }
    
    printf("client connect succeed, socket: %d\n", fd);
    return 0;
}

void setClientState(SocketArray *array, int fd, int state)
{
    int i;
    
    for (i=0; i<array->count; i++)
    {
        if (array->s[i].socket == fd)
        {
            array->s[i].state = state;
            break;
        }
    }
}

例程编译及运行

TCP服务端例程编译及运行结果
#假设主机ip为192.168.201.28
$ gcc tcpserver.c -o tcpserver
$ ./tcpserver 0.0.0.0 66666
ip: 0.0.0.0, port: 66666
add new client[192.168.146.128:57654], socket: 4
add new client[192.168.146.128:57655], socket: 5
add new client[192.168.146.128:57656], socket: 6
add new client[192.168.146.128:57657], socket: 7
add new client[192.168.146.128:57658], socket: 8
client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
client[4]: Hello, i'm client[3], i will disconnect after sending data 9 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 9 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 9 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 9 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 9 times
client[4]: Hello, i'm client[3], i will disconnect after sending data 8 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 8 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 8 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 8 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 8 times
...
client[4]: Hello, i'm client[3], i will disconnect after sending data 1 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 1 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 1 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 1 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 1 times
socket exception, socket: 4
remove client, socket: 4
socket exception, socket: 6
remove client, socket: 6
socket exception, socket: 8
remove client, socket: 8
socket exception, socket: 5
remove client, socket: 5
socket exception, socket: 7
remove client, socket: 7
add new client[192.168.146.128:57855], socket: 4
add new client[192.168.146.128:57856], socket: 5
add new client[192.168.146.128:57857], socket: 6
add new client[192.168.146.128:57858], socket: 7
add new client[192.168.146.128:57859], socket: 8
client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
TCP客户端例程编译及运行结果
# 新打开一个终端
$ gcc tcpclient.c -o tcpclient
$ ./tcpclient 192.168.201.28 66666

ip: 192.168.146.128, port: 66666
create client, socket: 3
add client[3], client count: 1
create client, socket: 4
add client[4], client count: 2
create client, socket: 5
add client[5], client count: 3
create client, socket: 6
add client[6], client count: 4
create client, socket: 7
add client[7], client count: 5
client connect succeed, socket: 3
client connect succeed, socket: 4
client connect succeed, socket: 5
client connect succeed, socket: 6
client connect succeed, socket: 7
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 9 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 9 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 9 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 9 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 9 times
...
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 2 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 2 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 2 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 2 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 2 times
i'm client[3], i will disconnect now
i'm client[4], i will disconnect now
i'm client[5], i will disconnect now
i'm client[6], i will disconnect now
i'm client[7], i will disconnect now
No connected clients
create client, socket: 3
add client[3], client count: 1
create client, socket: 4
add client[4], client count: 2
create client, socket: 5
add client[5], client count: 3
create client, socket: 6
add client[6], client count: 4
create client, socket: 7
add client[7], client count: 5
client connect succeed, socket: 3
client connect succeed, socket: 4
client connect succeed, socket: 5
client connect succeed, socket: 6
client connect succeed, socket: 7
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times

网络编程之TCP协议例程小结

本节展示的例程只是基础的通信框架,还未嵌套较为完善的通信协议,感兴趣的小伙伴可以自行补充完善(期待大家的分享-)。后期有时间的话,我准备加入Modbus协议,欢迎小伙伴们积极收藏关注私信交流!

你可能感兴趣的:(Linux_C语言,网络,linux,c语言,开发语言,服务器,tcp/ip)