您现在的位置是:首页 >技术杂谈 >Linux下网络编程(3)——socket编程实战,如何构建一个服务器和客户端连接网站首页技术杂谈

Linux下网络编程(3)——socket编程实战,如何构建一个服务器和客户端连接

GPIOB_PIN7 2024-06-17 10:43:23
简介Linux下网络编程(3)——socket编程实战,如何构建一个服务器和客户端连接

        经过前几篇的介绍,本文我们将进行编程实战,实现一个简单地服务器和客户端应用程序。

编写服务器程序

        编写服务器应用程序的流程如下:

        ①、调用 socket()函数打开套接字,得到套接字描述符;

        ②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定;

        ③、调用 listen()函数让服务器进程进入监听状态;

        ④、调用 accept()函数获取客户端的连接请求并建立连接;

        ⑤、调用 read()/recv()、write()/send() 与客户端进行通信;

        ⑥、调用 close()关闭套接字。

        下面,我们就根据上面列举的步骤来编写一个简单的服务器应用程序,代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT     8888    //端口号不能发生冲突,不常用的端口号通常大于5000

int main(void){
    struct sockaddr_in server_addr = {0};
    struct sockaddr_in client_addr = {0};
    char ip_str[20] = {0};
    int sockfd, connfd;
    int addrlen = sizeof(client_addr);
    char recvbuf[512];
    int ret;

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 将套接字与指定端口号进行绑定 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("bind error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 使服务器进入监听状态 */
    ret = listen(sockfd, 50);
    if (0 > ret) {
        perror("listen error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 阻塞等待客户端连接 */
    connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
    if (0 > connfd) {
        perror("accept error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("有客户端接入...
");
    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
    printf("客户端主机的IP地址: %s
", ip_str);
    printf("客户端进程的端口号: %d
", client_addr.sin_port);

    /* 接收客户端发送过来的数据 */
    for ( ; ; ) {
        // 接收缓冲区清零
        memset(recvbuf, 0x0, sizeof(recvbuf));

        // 读数据
        ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
        if(0 >= ret) {
            perror("recv error");
            close(connfd);
            break;
        }
        // 将读取到的数据以字符串形式打印出来
        printf("from client: %s
", recvbuf);

        // 如果读取到"exit"则关闭套接字退出程序
        if (0 == strncmp("exit", recvbuf, 4)) {
            printf("server exit...
");
            close(connfd);
            break;
        }
    }
    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
}

        以上我们实现了一个非常简单地服务器应用程序,根据上面列举的步骤完成了这个示例代码,最终的功能是,当客户端连接到服务器之后,客户端会向服务器(也就是本程序)发送数据,在我们服务器应用程序中会读取客户端发送的数据并将其打印出来,就是这么简单的一个功能。

        SERVER_PORT 宏指定了本服务器绑定的端口号,这里我们将端口号设置为 8888,端口不能与其它服务器的端口号发生冲突,不常用的端口号通常大于 5000。

        另外,bind绑定的IP地址是 INADDR_ANY,即表示所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都由这个服务端进程进行处理。

编写客户端程序

        接下来我们再编写一个简单地客户端应用程序,客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入。示例代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT		8888          	//服务器的端口号
#define SERVER_IP   	"192.168.1.150"	//服务器的IP地址

int main(void){
    struct sockaddr_in server_addr = {0};
    char buf[512];
    int sockfd;
    int ret;

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 调用connect连接远端服务器 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);  //端口号
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址
    ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("connect error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("服务器连接成功...

");

    /* 向服务器发送数据 */
    for ( ; ; ) {
        // 清理缓冲区
        memset(buf, 0x0, sizeof(buf));

        // 接收用户输入的字符串数据
        printf("Please enter a string: ");
        fgets(buf, sizeof(buf), stdin);

        // 将用户输入的数据发送给服务器
        ret = send(sockfd, buf, strlen(buf), 0);
        if(0 > ret){
            perror("send error");
            break;
        }

        //输入了"exit",退出循环
        if(0 == strncmp(buf, "exit", 4))
            break;
    }
    close(sockfd);
    exit(EXIT_SUCCESS);
}

        代码不再说明!需要注意的是 SERVER_IP 和 SERVER_PORT 指的是服务器的 IP 地址和端口号,服务器的 IP 地址根据实际情况进行设置,服务器应用程序服务器端代码中我们绑定的端口号为 8888,所以在客户端应用程序中我们也需要指定 SERVER_PORT 为 8888。

编译测试

        这里笔者将服务器程序运行在开发板上,而将客户端应用程序运行在 Ubuntu 系统,当然你也可以将客户端和服务器程序都运行在开发板或 Ubuntu 系统,这都是没问题的。

        首先编译服务器应用程序和客户端应用程序:

         编译得到 client 和 server 可执行文件,server 可执行文件在开发板上运行,client 可执行文件在 PC 端 Ubuntu 系统下运行。将 server 可执行文件拷贝到开发板目录下,如下所示:

         在开发板执行 server:

        接着在 Ubuntu 系统执行客户端程序:

         客户端运行之后将会去连接远端服务器,连接成功便会打印出信息“服务器连接成功...”,此时服务器也会监测到客户端连接,会打印相应的信息,如下所示:

        接下来我们便可以在客户端处输入字符串,客户端程序会将我们输入的字符串信息发送给服务器,服务器接收到之后将其打印出来,如下所示:

Tips : 如果连接出现问题如“connect error: No route to host”,可以参考这篇问题记录开发板和虚拟机socket报错“connect error: No route to host”

 总结

        到此,socket编程的内容就告一段落,内容讲得非常浅,目的其实并不是让大家学会网络编程,旨在以引导大家入门为主,让大家对 socket 网络编程有一个基本的了解和认识。望诸君常学常新、学思践悟。 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。