linux系统socket通信编程1

摘要:
Linux下的套接字编程通常包括TcpSocket和UdpSocket,即RawSocket。其中,TCP和UDP模式的套接字编程用于在应用层编写套接字程序,而RawSocket的使用相对较少,这不在本文的范围内。服务器端的主要进程是一个死循环。它接受套接字连接,然后将其原封不动地返回给客户端。客户端退出后,将关闭套接字连接并再次接受下一个套接字连接。

Linux下的Socket编程大体上包括Tcp Socket、Udp Socket即Raw Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较多的,而Raw Socket则用得相对较少,不在本文介绍范围之列。

TCP Socket

基于TCP协议的客户端/服务器程序的一般流程一般如下:

linux系统socket通信编程1第1张

它基本上可以分为三个部分:

一、建立连接:

  1. 服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态
  2. 客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答
  3. 服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

二、传输数据:

建立连接后,TCP协议提供全双工的通信管道,服务器端和客户端根据协议可以通过read和write的反复调用实现数据的传输

三、关闭连接:

当数据传输已经完成后,服务器和客户端可以调用Close关闭连接,一端关闭连接后,另一端read函数则会返回0,可以根据这个特征来感应另一端的退出。

下面就以一个简单的EchoServer演示一下如何创建服务器端和客户端代码,其中和socket相关api都会高亮显示。

服务器端示例:

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

    #define MAXLINE 80
    #define SERV_PORT 8000

    int main(void)
    {
        char buf[MAXLINE];

        int listenfd = 0;
        listenfd = socket(AF_INET, SOCK_STREAM, 0);

        sockaddr_in servaddr = {0};
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);

        bind(listenfd, (sockaddr *)&servaddr, sizeof(servaddr));
        listen(listenfd, 20);

        printf("Accepting connections ... ");
        while (1)
        {
            sockaddr_in cliaddr = {0};
            socklen_t cliaddr_len = sizeof(cliaddr);
            int connfd = accept(listenfd, (sockaddr *)&cliaddr, &cliaddr_len);
    
            char str[INET_ADDRSTRLEN];
            printf("connected from %s at PORT %d ",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));

            while(true)
            {
                int count = read(connfd, buf, MAXLINE);
                if (count == 0)
                    break;

                write(connfd, buf, count);
            }

            close(connfd);
            printf("closed from %s at PORT %d ",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));
        }
    }

PS:这里需要注意的一下的是sock函数的第二个参数SOCK_STREAM,它表示是一个TCP连接,后面我们会介绍通过传入SOCK_DGRAM打开udp连接。

服务器端主体流程就是一个死循环,它接受一个socket连接,然后将其原封不动的返回给客户端,待客户端退出后,关闭socket连接,再次接受下一个socket连接。

客户端代码如下:

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

    #define MAXLINE 80
    #define SERV_PORT 8000
    #define MESSAGE "hello world"

    int main(int argc, char *argv[])
    {
        char buf[MAXLINE];

        int sockfd = socket(AF_INET, SOCK_STREAM, 0);

        sockaddr_in servaddr = {0};
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);

        if (0 != connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)))
        {
            printf("connected failed");
            return 1;
        }

        write(sockfd, MESSAGE, sizeof(MESSAGE));
        int count = read(sockfd, buf, MAXLINE);

        printf("Response from server: %s ",buf);

        close(sockfd);
        return 0;
    }

客户端代码比较简单,这里就不多介绍了。

UDP Socket

典型的UDP客户端/服务器通讯过程如下图所示:

linux系统socket通信编程1第2张

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,可能反而会需要更多代码。

典型的示例如下:

    /* server.cpp */
    #include <stdio.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    #define MAXLINE 80
    #define SERV_PORT 8000

    int main(void)
    {
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];

        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        sockaddr_in servaddr = {0};
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);

        bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));

        printf("Accepting connections ... ");
        while (1)
        {
            sockaddr_in cliaddr;
            socklen_t cliaddr_len = sizeof(cliaddr);

            int count = recvfrom(sockfd, buf, MAXLINE, 0, (sockaddr *)&cliaddr, &cliaddr_len);
            if (count < 0)
            {
                printf("recvfrom error");
                continue;
            }

            printf("received from %s at PORT %d ",
                 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                 ntohs(cliaddr.sin_port));

            sendto(sockfd, buf, count, 0, (sockaddr *)&cliaddr, sizeof(cliaddr));
        }
    }

    /* client.cpp */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    #define MAXLINE 80
    #define SERV_PORT 8000

    int main(int argc, char *argv[])
    {
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];
    
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        sockaddr_in servaddr = {0};
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);

        while (fgets(buf, MAXLINE, stdin) != NULL)
        {
            int count = sendto(sockfd, buf, strlen(buf), 0, (sockaddr *)&servaddr, sizeof(servaddr));
            if (count == -1)
            {
                printf("sendto error");
                return 0;
            }

            count = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
            if (count == -1)
            {
                printf("recvfrom error");
                return 0;
            }
    
            write(STDOUT_FILENO, buf, count);
        }

        close(sockfd);
        return 0;
    }

免责声明:文章转载自《linux系统socket通信编程1》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇[.Net 5.0] 5.终于搞定了输出路径较好的第三方下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Django项目:CMDB(服务器硬件资产自动采集系统)--11--07CMDB文件模式测试采集硬件数据

1 #settings.py 2 # ————————01CMDB获取服务器基本信息———————— 3 import os 4 5 BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))##当前路径 6 7 # 采集资产的方式,选项有:a...

linux 下处理大文件

1、head tail more 2、先把大文件进行分割 split split参数: -a, --suffix-length=N 指定输出文件名的后缀,默认为2个 -b, --bytes=SIZE 指定输出文件的字节数 -C, --line-bytes=SIZE 每一输出档中,单行的最大 byte数 -d, --numer...

debian内核代码执行流程(一)

 本文根据debian开机信息来查看内核源代码。 系统使用《debian下配置dynamic printk以及重新编译内核》中内核源码来查看执行流程。 使用dmesg命令,得到下面的开机信息: [ 0.000000] Initializing cgroup subsys cpuset [ 0.000000] Initializing cgrou...

Linux操作临时文件

使用临时文件要考虑几个问题: 保证临时文件间的文件名不互助冲突。 保证临时文件中内容不被其他用户或者黑客偷看、删除和修改。 Linux中提供了mkstemp 和 tmpfile 函数来处理临时文件。 mkstemp函数 int mkstemp(char *template); mkstemp函数在系统中以唯一的文件名创建一个文件并打开,而且只有当前用户...

.NET作品集:linux下的博客程序

博客程序架构 本博客程序是博主11年的时候参考loachs小泥鳅博客内核开发的.net跨平台博客cms,距今已有6年多了,个人博客网站一直在用,虽然没有wordpress那么强大,但是当时在深究.net的同时,自己能写一个博客程序,并且基于独立Linux服务器搭建一个自己的.net网站还是挺有意思,毕竟当年运行在linux下的.net网站还是极少数的,之前的...

交叉编译工具链(详解)

交叉编译工具链  1、嵌入式开发模型-交叉开发     在嵌入式开发过程中有宿主机和目标机的角色之分:宿主机是执行编译、链接嵌入式软件的计算机;目标机是运行嵌入式软件的硬件平台。                在宿主机执行编译的流程如下:                   2、交叉编译工具链详解 参考: http://www.crifan.com/fi...