网络编程-------基于TCP协议的客户端--服务器socket实例

更多linux知识点:linux目录索引


1. socket

  socket指的是某一主机的 ip地址和端口号

2. socket常见API

  1. 创建socket

      #include <sys/types.h>          /* See NOTES */
      #include <sys/socket.h>
      int socket(int domain, int type, int protocol);
      //个人理解:客户端和服务器之间的通信,其实也是进程间的通信,
      //但是这是在网络中的,那么进程在网络中发送数据是要知道对方
      //的端口号和ip地址才能将数据发送给对方,所以创建socket
      //是存进程的ip和端口号

    参数

    1. domain: 表示哪一层的网络协议,有ipv4和ipv6等其他选择
    2. type:表示你要以什么形式进程数据传输;例如:面向数据包还是面向字节流
    3. protocol:一般传0,因为前面两个参数已经确定了要将数据发送给谁
  2. 绑定端口号

        #include <sys/types.h>          /* See NOTES */
        #include <sys/socket.h>
        int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
    //关于绑定(个人理解):
    //绑定是为了将服务器的ip和端口号写入创建的socket文件里面,
    //创建的socket的文件没有东西,此时将自己的套接字信息写入文件;
    //如果没有写套接字信息,系统就会默认分配;此时写入是为了固定
    //死套接字信息,告诉所有客户端是一个固定的服务器端口号和ip;
    //如果让系统自动分配,那么第二次启动时,服务器的ip和端口号不一致
    //就会导致客户端在此连接服务器时,找不到服务器

    参数

    1. sockfd:刚才创建的套接字
    2. const struct sockaddr* addr:这是一个结构体的指针,用于服务器自己的套接字信息,这样才能将保证服务器的套接字信息是固定的
    3. len:结构的大小
  3. 监听

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int listen(int sockfd, int backlog);
       //监听:用于TCP协议进行传输时,服务器需要进行监听,
       //因为基于TCP的数据传输是面向连接的,并且是一对多的,
       //可能有多个客户端来连接服务器,此时将服务器设定成监听状态,
       //谁连接了服务器,服务器就跟谁建立连接,建立连接后,进行数据传输

    参数:

    1. sockfd:用于监听的套接字,服务器设置成监听状态,查看有哪个客户端想来连接我
    2. backlog:表示可以连接的客户端个数
  4. 接受请求

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       //接受请求:用于服务器获取客户端的连接;这里的返回值也是
       //一个文件描述符,指向的也是套接字文件,这里用于客户端和
       //服务器端之间进行通信;和上面的监听套接字不一样,监听状态
       //要一直存在,查看有没有客户端来连接我

    参数

    1. sockfd:监听套接字,这是一个媒介,当客户端来了,创建一个套接字存自己和客户的信息,让监听套接字继续监听
    2. 结构体指针:这里的结构体存放的是客户端的ip和端口号;这是给程序员看的,其实也就是给人看的,如果你想知道客户端的ip和端口号,就接受,如果你不关心,可以写NULL
    3. 结构体大小的指针:这里是一个传出传入型参数,将客户端的ip和端口号存入,必定要知道大小
  5. 收数据

        #include <sys/types.h>
        #include <sys/socket.h>
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       //由于TCP是面向连接的,前面也已经知道我连的是哪个客户端了,
       //在这里可以直接进行通信,进行收数据和发数据
      参数:
      sockfd:刚才建立连接时创建的socket文件描述符
      buf:你接受的数据存放的缓冲区
      len:你期望读多少数据
      flags:0表示阻塞式接收数据,非0表示非阻塞时收数据`
  6. 发数据

       #include <sys/types.h>
       #include <sys/socket.h>
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    //和收数据是相对的,这里不在解释
  7. 建立连接

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
    //这里的连接指的是客户端主动去连接服务器,因为服务器只有一个,
    //客户端有多个,客户端要主动去连服务器才可以建立连接

    参数:

    1. sockfd:和服务器创建socket文件的作用是一样的,不在多做解释
    2. 存ip地址和端口号:既然我要去连接服务器,就要知道连接的哪个服务器,不能乱连
    3. 结构体长度:ip和端口号存在结构体中,将结构体的首地址传过去,必定要知道大小

3. TCP协议的传输特点

  1. 面向连接:服务器一旦和客户端建立连接,就需要为此分配资源,建立连接关系,当断开连接时,需要释放资源
  2. 面向字节流:以字节来读取和写入,字节数的大小完全取决于自己
  3. 可靠传输:由于服务器已经和客户端建立了通信,就会保证数据能够传输成功,不会存在丢数据的现象

4. 客户端和服务器传输流程图

这里写图片描述

5. 基于多线程的客户端服务器传输

服务器端:

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


pthread_t tid;

void* ProcessWorker(void* arg)
{
    int fd = *(int*)arg;

    char buf[128];
    while(1){//不断从客户端拿数据,在发回给客户端
        ssize_t s = read(fd, buf, sizeof(buf));
        if(s < 0){
            perror("read");
            break;
        }
        else if(s == 0){
            printf("client quit...\n");
            break;
        }


        buf[s] = 0;
        printf("client# %s\n",buf);

        send(fd, buf, strlen(buf), 0);
    }
    close(fd);
}
// ./server  ip  port
int main(int argc, char* argv[])
{
    if(argc != 3){
        printf("Usage ./server [ip] [port]\n");
        return 1;
    }

    //创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0){
        perror("socket");
        return 2;
    }

    //绑定端口号
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));

    if(bind(sockfd, (struct sockaddr*)&server, sizeof(server)) < 0){//绑定失败
        perror("bind");
        return 3;
    }

    //监听
    if(listen(sockfd, 5) < 0){
        perror("listen");
        return 4;
    }

    struct sockaddr_in client;//用于接收到客户端的ip和端口号
    socklen_t len = sizeof(client);

    char buf[128];
    for(;;){//服务器一直运行

        int clientfd = accept(sockfd, (struct sockaddr*)&client, &len);

        if(clientfd < 0){//当没有接收到客户端,尝试重新接收
            perror("accept");
            continue;
        }

        printf("Get connet [%s] [%d]\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));

        //创建线程,主线程去监听,新线程去处理
         pthread_create(&tid, NULL, ProcessWorker, (void*)&clientfd);
         //在这里不能关闭文件描述符,因为主线程和新线程看到的是一个文件描述符,当把文件描述符关闭后,新线程无法获取从客户端发送的请求或消息
         pthread_detach(tid);//并将新线程分离出去
    }
    return 0;
}

服务器端:

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

// ./client  ip地址  端口号
int main(int argc,char* argv[])
{
    //进程 ip地址  端口号
    if(argc != 3){
        perror("Usage ./server [ip] [port]\n");
        return 1;
    }
    //ipv4 面向字节流  创建socket
    int sock = socket(AF_INET, SOCK_STREAM,0);
    if(sock < 0){
        perror("socket");
        return 2;
    }


    struct sockaddr_in server;//绑定的是服务器的ip和端口号
    server.sin_family = AF_INET;//协议族
    server.sin_addr.s_addr = inet_addr(argv[1]);//将ip专程4字节ip地址,在转成大端字节序
    server.sin_port = htons(atoi(argv[2]));

    //连接服务器
    int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));
    if(ret < 0){//连接失败
        perror("connect");
        return 3;
    }

    //表示连接成功
    printf("Connect success...\n");

    char buf[1024];
    while(1){
        printf("client: ");
        fflush(stdout);

        ssize_t s = read(0, buf, sizeof(buf)-1);
        if(s > 0){//读取数据
            buf[s-1] = 0;

            write(sock, buf, strlen(buf));
        }
        else{
            break;
        }

        ssize_t rd = read(sock, buf, sizeof(buf)-1);
        if(rd > 0){
            buf[rd] = 0;
            printf("server# %s\n",buf);
        }


    }

    close(sock);
    return 0;
}

6. 基于多进程的客户端服务器传输

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


// ./server  ip  port
int main(int argc, char* argv[])
{
    if(argc != 3){
        printf("Usage ./server [ip] [port]\n");
        return 1;
    }

    //创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0){
        perror("socket");
        return 2;
    }

    //绑定端口号
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));

    if(bind(sockfd, (struct sockaddr*)&server, sizeof(server)) < 0){//绑定失败
        perror("bind");
        return 3;
    }

    //监听
    if(listen(sockfd, 5) < 0){
        perror("listen");
        return 4;
    }

    struct sockaddr_in client;//用于接收到客户端的ip和端口号
    socklen_t len = sizeof(client);

    char buf[128];
    for(;;){//服务器一直运行

        int clientfd = accept(sockfd, (struct sockaddr*)&client, &len);
        if(clientfd < 0){//当没有接收到客户端,尝试重新接受
            perror("accept");
            continue;
        }

        printf("Get connet [%s] [%d]\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));

        pid_t pid = fork();
        if(pid == 0){//子进程创建孙子进程,然后退出
            if( fork() == 0){//创建的子进程,去处理客户端发送过来的消息

                //1.从客户端接受消息,在将其发送回去
                while(1){//子进程不断从客户端读数据,在将其发回去
                    ssize_t  s = recv(clientfd, buf, sizeof(buf)-1, 0);
                    if(s > 0){
                        buf[s] = 0;
                        printf("client# %s\n",buf);

                        ssize_t ret = send(clientfd, buf, strlen(buf), 0);
                        if(ret < 0){
                            perror("send");
                           return 1;
                        }
                    }
                    else if(s == 0){//客户端退出
                        printf("client quit...\n");
                        break;
                    }
                    else{
                        break;
                    }
                }
                exit(0);//自己退出
            }
          else if(pid > 0){//回收子进程,继续监听有没有客户端
                waitpid(pid, NULL, 0);
                close(clientfd);
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80787556