采用TCP协议的C/S架构示例(1)

2018.12.18

  本文是一个TCP通讯的示例,分为服务器和客户端两部分。
  服务器端47.98.140.167创建套接字socket,并与端口11014绑定;
  然后使套接字处于监听listen状态,调用accept等待来自客户端的连接请求;
  收到客户端的连接请求后与客户端建立连接;
  最后接收客户端发来的消息并打印出来。

  客户端创建套接字socket,然后连接connect到服务器47.98.140.167的11014端口;
  使用connect返回的连接套接字与服务器通信,交换数据。

一、模块封装

  我们将一些通用的代码封装起来,便于使用。
  通用网络封装代码头文件 tcp_net_socket.h。

#ifndef	__TCP__NET__SOCKET__H
#define	__TCP__NET__SOCKET__H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

extern int tcp_init(const char* ip, int port);
extern int tcp_accept(int sfd);
extern int tcp_connect(const char* ip, int port);
extern void signalhandler(void);
#endif

  具体的函数封装如下(tcp_net_socket.c)。

#include \"tcp_net_socket.h\"
#define LISTENQ 12 // 最多监听LISTENQ个连接

int tcp_init(const char* ip, int port) //服务器端套接字的初始化
{
        // create a new socket
        int sfd;
        if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
                perror(\"socket\");
                exit(-1);
        }

        //允许重复使用port与套接字进行绑定
        int optval = 1;
        if(setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (void *)&optval, sizeof(int)) == -1) {
                perror(\"setsockopt\");
                close(sfd);
                exit(-1);
        }

        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(struct sockaddr_in));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(port);

        //将socket与指定的ip、port绑定
        if(bind(sfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) == -1) {
                perror(\"bind\");
                close(sfd);
                exit(-1);
        }

        // 最多监听LISTENQ个连接
        if(listen(sfd, LISTENQ) == -1) {
                perror(\"listen\");
                close(sfd);
                exit(-1);
        }

        return sfd;
}

/* 用于服务器端的接收,返回新的代表客户端的套接字
 * 之后可以利用这个新的套接字与客户端交换数据
 * sfd套接字继续等待客户端的连接请求
 */
int tcp_accept(int sfd) //用于服务器端的接收
{
        struct sockaddr_in cli_addr;
        memset(&cli_addr, 0, sizeof(struct sockaddr_in));
        int cli_addr_len = sizeof(struct sockaddr_in);

        //sfd接受客户端连接,并创建新的socket为new_fd,
        //将请求连接的客户端IP、port保存在结构体cli_addr中
        int new_fd;
        if((new_fd = accept(sfd, (struct sockaddr *)&cli_addr, &cli_addr_len)) == -1) {
                perror(\"accept\");
                close(sfd);
                exit(-1);
        }
        printf(\"%s:%d connect come in!\\n\", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

        return new_fd;
}

int tcp_connect(const char* ip, int port) //用于客户端的连接
{
        int sfd;
        if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { //注册新的socket
                perror(\"socket\");
                exit(-1);
        }

        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(struct sockaddr_in));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr(ip);
        serv_addr.sin_port = htons(port);

        //将sfd连接至指定的服务器网络地址serv_addr
        if(connect(sfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr_in)) == -1) {
                perror(\"connect\");
                close(sfd);
                exit(-1);
        }

        return sfd;
}

void signalhandler(void) //用于信号处理,让服务器端在按下Ctrl+C或Ctrl+\\时不会退出
{
        sigset_t sigSet;
        sigemptyset(&sigSet);
        sigaddset(&sigSet, SIGINT);
        sigaddset(&sigSet, SIGQUIT);
        sigprocmask(SIG_BLOCK, &sigSet, NULL);
}

二、服务器端的代码

  服务器端:tcp_net_server.c

#include \"tcp_net_socket.h\"
#define REVBUF 512

int main(int argc, char* argv[])
{
        if(argc < 3) {
                printf(\"Usage: ./servertcp ip port\\n\");
                exit(-1);
        }

        // signalhandler();

        /* eg: int sfd = tcp_init(\"192.168.1.130\", 8888); */
        int sfd = tcp_init(argv[1], atoi(argv[2]));
        char revbuf[REVBUF] = {0};
        int revbytes;

        int cfd = tcp_accept(sfd);
        while(1) {
                revbytes = recv(cfd, revbuf, sizeof(revbuf), 0); // received date from client cfd, save data to revbuf[]
                if(revbytes == -1) {
                        perror(\"recv\");
                        close(cfd);
                        close(sfd);
                        exit(-1);
                } else if(revbytes == 0) {                     //if client closed, ret == 0
                        close(cfd);
                        break;
                }
                printf(\"$ %s\", revbuf);

                // char str[] = \"hello world!\"; 
                // if(send(cfd, str, strlen(buf), 0) == -1) { //send str[] to client cfd
                if(send(cfd, revbuf, revbytes, 0) == -1) {
                        perror(\"send\");
                        close(cfd);
                        close(sfd);
                        exit(-1);
                }
                memset(revbuf, 0, sizeof(revbuf));
        }
        close(cfd);
        close(sfd);

        return 0;
}

三、客户端的代码

  客户端:tcp_net_client.c

#include \"tcp_net_socket.h\"

int main(int argc, char* argv[])
{
        if(argc < 3) {
                printf(\"Usage: ./clienttcp ip port\\n\");
                exit(-1);
        }
        /* eg: int sfd = tcp_connect(\"192.168.1.130\", 8888); */
        int sfd = tcp_connect(argv[1], atoi(argv[2]));
/*
        char buf[512] = {0};
        send(sfd, \"hello\", 6, 0); //send \"hello\" to sfd server
        recv(sfd, buf, sizeof(buf), 0); //receive data from sfd server
        puts(buf);
        close(sfd);
*/
        char sbuf[1024] = {0};
        char rbuf[1024] = {0};
        printf(\"$ \");

        while(fgets(sbuf, sizeof(sbuf), stdin) != NULL) {
                send(sfd, sbuf, strlen(sbuf), 0);
                recv(sfd, rbuf, sizeof(rbuf), 0);
                printf(\"# %s$ \", rbuf);
                memset(sbuf, 0, sizeof(sbuf));
                memset(rbuf, 0, sizeof(rbuf));
        }
        printf(\"close sfd\\n\");
        close(sfd);
}

四、编译

gcc -o tcp_net_server tcp_net_server.c tcp_net_socket.c
gcc -o tcp_net_client tcp_net_client.c tcp_net_socket.c

  如果客户端是ARM40-A5,需将gcc换成相应的arm gcc。

五、执行

  先执行服务器端的程序:
./tcp_net_server 192.168.1.130 11014
  在执行客户端程序:
./tcp_net_client 192.168.1.130 11014

  在客户端输入:

./tcp_net_client 192.168.1.130 11014
$ 123
# 123
$ 345
# 345
$ 456
# 456

  在服务器端得到:

./tcp_net_server 192.168.1.130 8888
192.168.1.130:55794 connect OK!
$ 123
$ 345
$ 456

  这种方式只能建立一个连接,希望建立多个连接,见《采用TCP协议的C/S架构示例(2)》。

参考文章

  《高质量嵌入式Linux C编程》
  《从实践中学嵌入式Linux应用程序开发》
  《UNIX环境高级编程第3版》

收藏 打印