6

socket编程实现tcp服务器_C/C++ - 云梦士

 2 years ago
source link: https://www.cnblogs.com/yunmeng-shi/p/16219550.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1. 需求分析

实现一个回声服务器的C/S(客户端client/服务器server)程序,功能为客户端连接到服务器后,发送一串字符串,服务器接受信息后,返回对应字符串的大写形式给客户端显示。
例如:

客户端发送“this is a webserver example!",

服务器返回"THIS IS A WEBSERVER EXAMPLE!"

2. 项目实现

2.1 服务器端程序echo_server.c

#include <stdio.h>      //printf
#include <stdlib.h>     //exit
#include <unistd.h>     //read, write, close
#include <sys/types.h>  //socket, bind, listen, accept
#include <sys/socket.h> //socket, bind, listen, accept
#include <string.h>     //strerror
#include <ctype.h>      //inet_ntop
#include <arpa/inet.h>  //inet_ntop
#include <errno.h>      //strerror

#define SERVER_PORT 666

//出错处理
void perror_exit(const char* des) {
    fprintf(stderr, "%s error, reason: %s\n", des, strerror(errno));
    exit(1);
}

int main(void){

    int sock;//代表信箱
    int ret;//作为bind和listen的返回值,用于处理出错信息
    struct sockaddr_in server_addr;


    //1.创建套嵌字(信箱)。成功:返回socket的文件描述符,失败:返回-1,设置errno
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        perror_exit("create socket");
    }

    //2.清空服务器地址空间(标签),写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;//选择协议族IPV4
    //inet_pton(AF_INET, "1.1.1.1", &server_addr.sin_addr.s_addr);//测试出错处理函数perror_exit
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);//绑定端口号

    //3. 实现标签贴到收信得信箱上
    ret = bind(sock, (struct sockaddr *)&server_addr,  sizeof(server_addr));
    if(ret == -1) {
        perror_exit("bind");
    }

    //4. 把信箱挂置到传达室,这样,就可以接收信件了(监听客户端)
    ret = listen(sock, 128);
    if(ret == -1) {
        perror_exit("listen");
    }

    //万事俱备,只等来信
    printf("等待客户端的连接\n");

    //5. 处理客户端请求
    int done =1;
    while(done){
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        socklen_t  client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
                 inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
                 ntohs(client.sin_port));
        /*读取客户端发送的数据*/
        len = read(client_sock, buf, sizeof(buf)-1);
        buf[len] = '\0';
        printf("receive[%d]: %s\n", len, buf);

        //转换成大写
        for(i=0; i<len; i++){
            buf[i] = toupper(buf[i]);
        }


        len = write(client_sock, buf, len);

        printf("finished. len: %d\n", len);
        close(client_sock);

    }
    
    //6. 关闭连接
    close(sock);
    return 0;
}

2.2 客户端程序echo_client.c

#include <stdio.h>      //printf
#include <stdlib.h>     //exit
#include <string.h>     //memset, strlen
#include <unistd.h>     //read, write, close
#include <sys/socket.h> //socket, connect
#include <arpa/inet.h>  //inet_pton

#define SERVER_PORT 666
#define SERVER_IP  "127.0.0.1"
int main(int argc, char *argv[]){//argc表示传入命令的个数,argv表示传入的具体信息

    int sockfd;
    char *message;
    struct sockaddr_in servaddr;
    int n;
    char buf[64];

    //异常处理
    if(argc != 2){
        fputs("Usage: ./echo_client message \n", stderr);
        exit(1);
    }

    message = argv[1];//传入的信息
    printf("message: %s\n", message);

    //1. 创建套嵌字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&servaddr, '\0', sizeof(struct sockaddr_in));//分配空间

    //定义地址IP和端口
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVER_PORT);

    //2. 连接服务器
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    //3. 读写和服务器的交互信息
    write(sockfd, message, strlen(message));

    n = read(sockfd, buf, sizeof(buf)-1);

    if(n>0){
        buf[n]='\0';
        printf("receive: %s\n", buf);
    }else {
        perror("error!!!");
    }

    printf("finished.\n");

    //4. 关闭连接
    close(sockfd);

    return 0;
}

3. 程序运行方式

我的echo_server.c程序在/share/echo_server文件夹下,echo_client.c程序在/share/echo_client文件夹下。
必须先运行服务器程序,再运行客户端程序。顺序不能反!!

3.1. 运行服务器程序

  • 首先,进入echo_server.c所在文件夹
    root@lxb-virtual-machine:/# cd /share/echo_server
  • 之后,编译程序
    root@lxb-virtual-machine:/share/echo_server# gcc echo_server.c -o echo_server
  • 最后运行程序
  • root@lxb-virtual-machine:/share/echo_server# ./echo_server

全过程截图:

3.2. 运行客户端程序

  • 首先,进入echo_client.c所在文件夹
    root@lxb-virtual-machine:/# cd /share/echo_client
  • 之后,编译程序
    root@lxb-virtual-machine:/share/echo_client# gcc echo_client.c -o echo_client
  • 最后运行程序
  • root@lxb-virtual-machine:/share/echo_client# ./echo_client "this is a webserver example!"

全过程截图:

首先我们进行感性的分析,用来理解各个步骤的用意。之后我们需要对里面涉及到的函数进行具体的分析。

4.1 李华写信模型

我们在高中英语经常遇到的一道作文题就是“你是李华,请给国外的笔友Andy写信”,而我们网络通信也可以类比于“李华与国外笔友通信”的模型。这里我们将“李华”比作客户端,“国外笔友Andy”作为服务器端。

4.1.1 Andy应该怎么做呢?

为了使“李华同学”与“国外笔友”能够交流,首先需要统一语言,同时约定好寄信方式,邮局寄信还是电子邮件之类的,(这就是“socket套嵌字”)。之后Andy准备好一个信箱,之后找一张标签纸(server_addr),整理干净这张标签纸(bzero函数),往上面写上自己的地址和门牌号,一切准备好后,将贴好标签纸的信箱挂到外面(listen函数),这样大家都能给Andy寄信。最后Andy只需要时不时去看看信箱有没有信,有的话把信的内容读出来(read函数),之后再写封回信寄回去(write函数)。

4.1.2 李华应该怎么做?

李信作为写信人就比较简单了,首先还是使用统一的寄信方式,往信封上写上自己要寄的地址和门牌号,也就是Andy家的地址和门牌号,之后与Andy联系上(connect函数)。接下来就可以给Andy写信(write函数),读Andy的回信(read函数)。收到回信,不想再和Andy通信了,这时就把两个人的联系断开(close函数)。

4.2 程序流程图

4.3 具体函数解析

4.3.1 socket函数

  1. 所属头文件

    #include <sys/socket.h>

  2. int socket(int domain, int type, int protocol);

    • domain:
      AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
      AF_INET6 与上面类似,不过是来用IPv6的地址
      AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
    • type:
      SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
      SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
      SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
      SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
      SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
    • protocol:
      传0 表示使用默认协议。
    • 返回值
      成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno

4.3.2 bind函数

  1. 所属头文件

    #include <sys/socket.h>

  2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • sockfd:
      socket文件描述符
    • addr:
      IP地址加端口号
    • addrlen:
      addr的长度
    • 返回值
      成功:返回0,失败:返回-1,设置errno

4.3.3 listen函数

  1. 所属头文件

    #include <sys/socket.h>

  2. int listen(int sockfd, int backlog);

    • sockfd:
      socket文件描述符
    • backlog:
      在Linux 系统中,它是指排队等待建立3次握手队列长度
    • 返回值
      成功:返回0,失败:返回-1,设置errno

4.3.4 accept函数

  1. 所属头文件

    #include <sys/socket.h>

  2. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    • sockfd:
      socket文件描述符
    • addr:
      IP地址加端口号
    • addrlen:
      addr的长度
    • 返回值
      成功:返回一个新的socket文件描述符,失败:返回-1,设置errno

4.3.5 connect函数

  1. 所属头文件

    #include <sys/socket.h>

  2. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • sockfd:
      socket文件描述符
    • addr:
      IP地址加端口号
    • addrlen:
      addr的长度
    • 返回值
      成功:返回一个新的socket文件描述符,失败:返回-1,设置errno

4.3.6 出错处理函数

  1. 所属头文件

    #include <errno.h>
    #include <string.h>
    
  2. char *strerror(int errnum);

    • errnum:
      错误编号的值,一般取 errno 的值
    • 返回值
      错误原因

感谢bilibili的Martin老师的视频: C语言/C++服务器开发】小白实现第一个服务器入门项目 网络通信与Socket 编程详解&源码分享,本篇博客也是基于Martin老师这个视频所做的。


Recommend

  • 35
    • www.tuicool.com 5 years ago
    • Cache

    [译]TCP Socket 是如何工作的?

    原文: How TCP Sockets Work by Evan Klitzke. 本文我将从上层介绍Linux上的TCP/IP栈是如何工作的,特别是socket系统调用和内核数据结构的交互、内核和实际网络的交...

  • 12

        以下是在实现一个高性能Socket组件总结下来的问题,如果你只需要处理几千的并发应用那代码编写上注意一下就行了,但需要面对上万或几万的并发应用.那以下问题的总结,相信对编写这方面的应用有很大的帮助. SocketAsyncEventArgs

  • 11
    • www.cnblogs.com 4 years ago
    • Cache

    C# Socket tcp 发送数据大小问题

    C# Socket tcp 发送数据大小问题     TCP/IP是可靠性传输协议,它能...

  • 7

    推荐一些socket工具,TCP、UDP调试、抓包工具 浏览:6004次  出处信息    还记得我在...

  • 10

    TCP SOCKET中backlog参数的用途是什么? 在前年时,业务中遇到好多次因为PHP-FPM的backlog参数引发的性能问题,一直想去详细研究一番,还特意在2013年总结里提到这事《为何PHP5.5.6中f...

  • 14

    先看一天面试的经验: 第一场: 面试官:你说一下TCP的三次握手 我:第一次Client将SYN置1……、第二次Server收……..、 第三次…….. 面试官:很难背吧? 我:……是啊,很难,要不我在和你...

  • 9
    • tonybai.com 3 years ago
    • Cache

    Go语言TCP Socket编程

    Go语言TCP Socket编程 Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分。在日常应用中,我们也可以看到Go中的net以及其subdir...

  • 8

    网络编程:基于TCP的socket网络传输视频(C++, python) Jun 12, 2017 1 minute read 可以实现C++ to C++、Python to Python、C++ to Python的视频或图像传输。 Socket的英文原义是“孔”或“插座”。作...

  • 7
    • ljlu1504.github.io 3 years ago
    • Cache

    TCP/UDP/SOCKET in Python

    TCP/UDP/SOCKET in Python 评分: 4.5 作者: Ryan Lu 类别: Python...

  • 3
    • www.cnblogs.com 2 years ago
    • Cache

    PX4配置过程与踩坑 - 云梦士

    由于需要在GitHub下载代码,而国内访问受限,可能会出现一些问题,这里建议使用github国内镜像,参看:GitHub国内镜像网站,当然下面会给出具体解决方案。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK