socket编程实例-TCP

TCP通信流程

// TCP 通信的流程
// 服务器端 (被动接受连接的角色) 
1. 创建一个用于监听的套接字
  - 监听:监听有客户端的连接   - 套接字:这个套接字其实就是一个文件描述符 2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)   - 客户端连接服务器的时候使用的就是这个IP和端口 3. 设置监听,监听的fd开始工作 4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字 (fd) 5. 通信   - 接收数据   - 发送数据
6. 通信结束,断开连接 // 客户端 1. 创建一个用于通信的套接字(fd) 2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信   - 接收数据   - 发送数据 4. 通信结束,断开连接

简单例子

// TCP通信的客户端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "10.82.220.132", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }

    
    // 3. 通信
    char recvBuf[1024] = {0};
    while(1) {

        char * data = "hello,i am client";
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}
client.c
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
recv server data : hello,i am server
// TCP 通信的服务器端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {0};
    while(1) {
        
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
            perror("read");
            exit(-1);
        } else if(num > 0) {
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}
server.c
client ip is 10.82.220.132, port is 58556
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client
recv client data : hello,i am client

多进程版服务端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>

void recyleChild(int arg) {
    while(1) {
        int ret = waitpid(-1, NULL, WNOHANG);
        if(ret == -1) {
            // 所有的子进程都回收了
            break;
        }else if(ret == 0) {
            // 还有子进程活着
            break;
        } else if(ret > 0){
            // 被回收了
            printf("子进程 %d 被回收了\n", ret);
        }
    }
}

int main() {

    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = recyleChild;
    // 注册信号捕捉
    sigaction(SIGCHLD, &act, NULL);
    

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 监听
    ret = listen(lfd, 128);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 不断循环等待客户端连接
    while(1) {

        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        // 接受连接
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
        if(cfd == -1) {
            if(errno == EINTR) {
                continue;
            }
            perror("accept");
            exit(-1);
        }

        // 每一个连接进来,创建一个子进程跟客户端通信
        pid_t pid = fork();
        if(pid == 0) {
            // 子进程
            // 获取客户端的信息
            char cliIp[16];
            inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
            unsigned short cliPort = ntohs(cliaddr.sin_port);
            printf("client ip is : %s, prot is %d\n", cliIp, cliPort);

            // 接收客户端发来的数据
            char recvBuf[1024];
            while(1) {
                int len = read(cfd, &recvBuf, sizeof(recvBuf));

                if(len == -1) {
                    perror("read");
                    exit(-1);
                }else if(len > 0) {
                    printf("recv client : %s\n", recvBuf);
                } else if(len == 0) {
                    printf("client closed....\n");
                    break;
                }
                write(cfd, recvBuf, strlen(recvBuf) + 1);
            }
            close(cfd);
            exit(0);    // 退出当前子进程
        }

    }
    close(lfd);
    return 0;
}
server_multi_process.c

多线程版服务端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

struct sockInfo {
    int fd; // 通信的文件描述符
    struct sockaddr_in addr;
    pthread_t tid;  // 线程号
};

struct sockInfo sockinfos[128];

void * working(void * arg) {
    // 子线程和客户端通信   cfd 客户端的信息 线程号
    // 获取客户端的信息
    struct sockInfo * pinfo = (struct sockInfo *)arg;

    char cliIp[16];
    inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp));
    unsigned short cliPort = ntohs(pinfo->addr.sin_port);
    printf("client ip is : %s, prot is %d\n", cliIp, cliPort);

    // 接收客户端发来的数据
    char recvBuf[1024];
    while(1) {
        int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf));

        if(len == -1) {
            perror("read");
            exit(-1);
        }else if(len > 0) {
            printf("recv client : %s\n", recvBuf);
        } else if(len == 0) {
            printf("client closed....\n");
            break;
        }
        write(pinfo->fd, recvBuf, strlen(recvBuf) + 1);
    }
    close(pinfo->fd);
    return NULL;
}

int main() {

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9998);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 监听
    ret = listen(lfd, 128);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 初始化数据
    int max = sizeof(sockinfos) / sizeof(sockinfos[0]);
    for(int i = 0; i < max; i++) {
        bzero(&sockinfos[i], sizeof(sockinfos[i]));
        sockinfos[i].fd = -1;
        sockinfos[i].tid = -1;
    }

    // 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信
    while(1) {

        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        // 接受连接
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);

        struct sockInfo * pinfo;
        for(int i = 0; i < max; i++) {
            // 从这个数组中找到一个可以用的sockInfo元素
            if(sockinfos[i].fd == -1) {
                pinfo = &sockinfos[i];
                break;
            }
            if(i == max - 1) { // 只允许128个线程
                sleep(1);
                i--;
            }
        }

        pinfo->fd = cfd;
        memcpy(&pinfo->addr, &cliaddr, len);

        // 创建子线程
        pthread_create(&pinfo->tid, NULL, working, pinfo);

        pthread_detach(pinfo->tid);
    }

    close(lfd);
    return 0;
}
server_multi_thread.c

 

 

第一版:实现简单功能

客户端从标准输入读入字符串传给服务端,服务端把字符串中的小写字母转化为大写字母传回客户端

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

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666

int main()
{
        int cfd;
        struct sockaddr_in serv_addr;
        socklen_t serv_addr_len;
        char buf[BUFSIZ];
        int n;

        cfd = socket(AF_INET, SOCK_STREAM, 0);

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        //serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);

        //客户端可以不用bind()绑定ip和端口号,操作系会默认分配

        connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        while(1)
        {
                fgets(buf, sizeof(buf), stdin);
                write(cfd, buf, strlen(buf));
                n = read(cfd, buf, sizeof(buf));
                write(STDOUT_FILENO, buf, n);
        }

        close(cfd);

        return 0;
}
client.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 6666

int main()
{
        int sfd, cfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        char buf[BUFSIZ];
        int n, i;

        sfd = socket(AF_INET, SOCK_STREAM, 0);

        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        listen(sfd, 128);

        clie_addr_len = sizeof(clie_addr);
        cfd = accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len);

        while(1)
        {
                n = read(cfd, buf, sizeof(buf));
                for(i=0; i<n; i++)
                {
                        buf[i] = toupper(buf[i]);
                }
                write(cfd, buf, n);
        }

        close(sfd);
        close(cfd);

        return 0;
}
server.c

 

第二版:加入错误封装

上面没有对错误进行处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数(新函数和原函数调用的区别仅仅在于新函数名首字母大写了),做成一个模块wrap.c,这样在调用socket函数时,调用我们封装了错误处理的函数就可以了。

#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
void perr_exit(const char *s)
{
    perror(s);
    exit(1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;
    again:
    if ( (n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");
    return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");
    return n;
}
int Listen(int fd, int backlog)
{
    int n;
    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");
    return n;
}
int Socket(int family, int type, int protocol)
{
    int n;
    if ( (n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");
    return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;
again:
    if ( (n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;
again:
    if ( (n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}
int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");
    return n;
}
ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;

    ptr = vptr;
    nleft = n;

    while (nleft > 0) {
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        } else if (nread == 0)
            break;
        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;

    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
again:
        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;    
        } else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;
    ptr = vptr;

    for (n = 1; n < maxlen; n++) {
        if ( (rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        } else if (rc == 0) {
            *ptr = 0;
            return n - 1;
        } else
            return -1;
    }
    *ptr = 0;
    return n;
}
wrap.c
#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
wrap.h

 

第三版:使用多进程技术在服务端实现高并发

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <sys/wait.h>

#include "wrap.h"

#define SERV_PORT 6666

void wait_child(int signo)
{
        while(waitpid(0, NULL, WNOHANG) > 0);
        return ;
}

int main()
{
        pid_t pid;
        int sfd, cfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        char buf[BUFSIZ], clie_IP[BUFSIZ];
        int n, i;

        sfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        Bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        Listen(sfd, 128);

        while(1)
        {
                clie_addr_len = sizeof(clie_addr);
                cfd = Accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
                printf("client IP: %s, port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port));

                pid = fork();
                if(pid < 0)
                {
                        perror("fork error");
                        exit(1);
                }
                else if(pid == 0)
                {
                        close(sfd);
                        break;
                }
                else
                {
                        close(cfd);
                        signal(SIGCHLD, wait_child);
                }
        }

        if(pid == 0)
        {
                while(1)
                {
                        n = Read(cfd, buf, sizeof(buf));
                        if(n == 0)
                        {
                                close(cfd);
                                return 0;
                        }
                        else if(n == -1)
                        {
                                perror("read error");
                                exit(1);
                        }
                        else
                        {
                                for(i=0; i<n; i++)
                                {
                                        buf[i] = toupper(buf[i]);
                                }
                                write(cfd, buf, n);
                                write(STDOUT_FILENO, buf, n);
                        }
                }
        }

        return 0;
}
server.c

 

第四版:使用多线程技术在服务端实现高并发

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>

#include "wrap.h"

#define SERV_PORT 6666
#define MAXLINE 8192

struct s_info{
        struct sockaddr_in cliaddr;
        int connfd;
};

void* do_work(void *arg)
{
        int n,i;
        struct s_info *ts = (struct s_info*)arg;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];

        while(1){
                n = Read(ts->connfd, buf, MAXLINE);
                if(n == 0){
                        printf("the client %d closed...\n", ts->connfd);
                        break;
                }
                printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port));
                for(i=0; i<n; i++){
                        buf[i] = toupper(buf[i]);
                }
                write(ts->connfd, buf, n);
                write(STDOUT_FILENO, buf, n);
        }
        Close(ts->connfd);

        return (void *)0;
}

int main()
{
        int sfd, cfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        struct s_info ts[256];
        pthread_t tid;
        int i = 0;

        sfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        Bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        Listen(sfd, 128);

        while(1)
        {
                clie_addr_len = sizeof(clie_addr);
                cfd = Accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
                ts[i].cliaddr = clie_addr;
                ts[i].connfd = cfd;

                pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
                pthread_detach(tid);
                i++;
        }

        return 0;
}
server.c

 

posted @ 2020-06-19 11:24  xd_xumaomao  阅读(214)  评论(0编辑  收藏  举报