TCP/UDP网络编程:网络字节序 socket() listen() bind() accept() connect() sendto() recvfrom() 线程池实现代码实例

1 网络字节序

  • 即大小端模式。TCP/IP规定网络数据流采用大端字节序,低地址表示高字节
  • 网络字节序和主机字节序的转换:h=host, n=network, l=32位long, s=16位short
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint32_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint32_t ntohs(uint16_t netshort);
  • sockaddr_in.sin_addr地址和字符串相互转换
#include <arpa/inet.h>

//将sockaddr_in.sin_addr地址转换成字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

//将字符串转换成sockaddr_in.sin_addr地址
const char *inet_pton(int af, const char *src, void *dst);

2 socket地址的数据结构

  • socket API早于ANSI C标准化,还没有void *类型,所以需要强制类型转换struct sockaddr *类型

3 TCP socket编程

1 TCP服务器端顺序:socket() bind() listen() accept() read()/write() close()

2 TCP客户端顺序:socket() connect() read()/write() close()

3.1 socket() 返回文件描述符

  • socket()第一个形参:1)SOCK_STREAM:TCP使用的流式类型;2)SOCK_DGRM:UDP使用的无连接数据包类型
#include <sys/types.h>
#include <sys/socket.h>
//成功返回0,失败返回-1并设置errno
int socket(int domain, int type, int protocol);

3.2 sockaddr_in结构体

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
raw_socket = socket(AF_INET, SOCK_RAW, protocol);

struct sockaddr_in {
    sa_family_t sin_family; //地址类型  IPv4-AF_INET
    int_port_t sin_port; //网络字节序端口号
    struct in_addr sin_addr; //网络字节序地址
}

struct in_addr {
    uint32_t s_addr; //网络字节序地址
}

3.3 bind()

#include <sys/types.h>
#include <sys/socket.h>

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

3.4 listen()

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

3.5 accept()

  • 服务端TCP三次握手在这里。注意此函数的套接字才是真正和客户端实际使用的套接字,之前的套接字只是建立监听而已
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

3.6 connect()

#include <sys/types.h>
#include <sys/socket.h>

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

4 TCP网络通信线程池代码实例

  • 线程池实现客户端与服务端的多线程并发通信:客户端发送标准输入到的数据给服务端,并且把服务端返回的数据显示到标准输出上;服务端把从客户端收到的数据返回给客户端,并且显示从客户端收到的数据

4.1 TCP服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#define err_msg(msg) {\
    perror(msg);\
    exit(1);\
}

//队列维护客户端到来的客户数量
typedef struct Task {
    int fd;
    struct Task *next;
} Task;

typedef struct Task_pool {
    Task *head, *tail;
    pthread_mutex_t lock;
    pthread_cond_t haveTask;
} Task_pool;

Task_pool *pool_init() {
    Task_pool *pool = (Task_pool *)malloc(sizeof(Task_pool));
    pool->head = NULL;
    pool->tail = NULL;
    pthread_mutex_init(&pool->lock, NULL);
    pthread_cond_init(&pool->haveTask, NULL);
    return pool;
}

//队列尾插入客户
void pool_push(Task_pool *pool, int fd) {
    pthread_mutex_lock(&pool->lock);

    Task *t = (Task *)malloc(sizeof(Task));
    t->fd = fd;
    t->next = NULL;

    if (pool->tail == NULL)
        pool->head = t, pool->tail = t;
    else
        pool->tail->next = t, pool->tail = t;

    pthread_cond_broadcast(&pool->haveTask); //条件发生,通知等待的线程去抢客户
    pthread_mutex_unlock(&pool->lock);
    return ;
}

Task pool_pop(Task_pool *pool) {
    pthread_mutex_lock(&pool->lock);

    //把锁临时释放,等待条件发生
    while (NULL == pool->head)
        pthread_cond_wait(&pool->haveTask, &pool->lock);

    Task task, *k;
    k = pool->head;
    task = *k;
    pool->head = pool->head->next;

    (pool->head == NULL) && (pool->tail = NULL);
    free(k);

    pthread_mutex_unlock(&pool->lock);
    return task;
}

void pool_free(Task_pool *pool) {
    pthread_mutex_lock(&pool->lock);

    Task *p = pool->head, *k;
    while (p) {
        k = p;
        p = p->next;
        free(k);
    }
    pool->head = NULL;

    pthread_mutex_lock(&pool->lock);
    pthread_mutex_destroy(&pool->lock);
    pthread_cond_destroy(&pool->haveTask);
    free(pool);
    return ;
}

//第三种:创建线程池
void *pfunc(void *arg) {
    pthread_detach(pthread_self());
    char buf[100];

    Task_pool *pool = (Task_pool *)arg;

    while (true) {
        Task task = pool_pop(pool); //客户出队
        int confd = task.fd, recv = 0;
        printf("[begin] client task confd= %d\n", confd);
        while (true) {
            recv = read(confd, buf, 100);
            if (!strncmp(buf, "exit", 4))
                break;
            write(1, buf, recv);

            write(confd, buf, recv);
        }
        printf("[end] client task confd= %d\n", confd);
        close(confd);
    }

    return (void *)0;
}

/*//第二种:使用多线程实现并发服务
void *pfunc(void *arg) {
    pthread_detach(pthread_self());
    int confd = (int)(long)arg, recv;
    char buf[100];
    while (true) {
        recv = read(confd, buf, 100);
        if (!strncmp(buf, "exit", 4))
            break;
        write(1, buf, recv);

        write(confd, buf, recv);
    }

    close(confd);
    return (void *)0;
}*/

int main() {
    int listenfd, confd, recv;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t client_len;
    char buf[100], arr[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN=16

    bzero(&serveraddr, sizeof(serveraddr)); //赋0值
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8080);
    serveraddr.sin_addr.s_addr = htons(INADDR_ANY); //(u_int32_t)0x00000000

    //第三种:创建线程池
    Task_pool *pool = pool_init();
    pthread_t tid;
    for (int i = 0; i < 8; ++i) { //假设是8核CPU
        pthread_create(&tid, NULL, pfunc, (void *)pool);
        printf("thread is: %#lx\n", tid);
    }

    //服务端第一步创建socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
        err_msg("socket");
    //第二步绑定
    if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
        err_msg("bind");
    //第三步监听
    if (listen(listenfd, 2) < 0) //客户端排队队列最多有2个
        err_msg("listen");
    //第四步accept
    printf("Accepting!\n");
    while (true) {
        client_len = sizeof(clientaddr);
        confd = accept(listenfd, (struct sockaddr *)&clientaddr, &client_len);
        if (confd < 0)
            err_msg("accept");
        printf("recv client ip: %s, port:%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr, arr, sizeof(arr)), ntohs(clientaddr.sin_port));

        pool_push(pool, confd); //客户入队

        /* //第一种:使用多进程实现并发服务
        pid_t pid = fork();
        if (pid < 0)
            err_msg("fork");
        if (pid > 0) {
            close(confd);
            while(waitpid(-1, NULL, WNOHANG));
            continue;
        }
        close(listenfd);

        while (true) {
            recv = read(confd, buf, 100);
            if (!strncmp(buf, "exit", 4))
                break;
            write(1, buf, recv);

            write(confd, buf, recv);
        }

        //第二种:使用多线程实现并发服务
        pthread_t tid;
        pthread_create(&tid, NULL, pfunc, (void *)confd);
        printf("thread is: %#lx\n", tid);

        close(confd);*/
    }

    pool_free(pool);
    return 0;
}

4.1.1 Linux TCP服务端:epoll+多线程web服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/epoll.h>
#define LEN 10000
#define err_msg(msg) {\
    perror(msg);\
    exit(1);\
}

//队列维护客户端到来的客户数量
typedef struct Task {
    int fd;
    struct Task *next;
} Task;

typedef struct Task_pool {
    Task *head, *tail;
    pthread_mutex_t lock;
    pthread_cond_t haveTask;
} Task_pool;

Task_pool *pool_init() {
    Task_pool *pool = (Task_pool *)malloc(sizeof(Task_pool));
    pool->head = NULL;
    pool->tail = NULL;
    pthread_mutex_init(&pool->lock, NULL);
    pthread_cond_init(&pool->haveTask, NULL);
    return pool;
}

//队列尾插入客户
void pool_push(Task_pool *pool, int fd) {
    pthread_mutex_lock(&pool->lock);

    Task *t = (Task *)malloc(sizeof(Task));
    t->fd = fd;
    t->next = NULL;

    if (pool->tail == NULL)
        pool->head = t, pool->tail = t;
    else
        pool->tail->next = t, pool->tail = t;

    pthread_cond_broadcast(&pool->haveTask); //条件发生,通知等待的线程去抢客户
    pthread_mutex_unlock(&pool->lock);
    return ;
}

Task pool_pop(Task_pool *pool) {
    pthread_mutex_lock(&pool->lock);

    //把锁临时释放,等待条件发生
    while (NULL == pool->head)
        pthread_cond_wait(&pool->haveTask, &pool->lock);

    Task task, *k;
    k = pool->head;
    task = *k;
    pool->head = pool->head->next;

    (pool->head == NULL) && (pool->tail = NULL);
    free(k);

    pthread_mutex_unlock(&pool->lock);
    return task;
}

void pool_free(Task_pool *pool) {
    pthread_mutex_lock(&pool->lock);

    Task *p = pool->head, *k;
    while (p) {
        k = p;
        p = p->next;
        free(k);
    }
    pool->head = NULL;

    pthread_mutex_lock(&pool->lock);
    pthread_mutex_destroy(&pool->lock);
    pthread_cond_destroy(&pool->haveTask);
    free(pool);
    return ;
}

//第四种:epoll+线程池
void *pfunc(void *arg) {
    pthread_detach(pthread_self());
    char buf[LEN];

    Task_pool *pool = (Task_pool *)arg;

    if (true) {
        Task task = pool_pop(pool); //客户出队
        int confd = task.fd, recv = 0;
        printf("[begin] client task confd= %d\n", confd);
        while (true) {
            recv = read(confd, buf, LEN);
            write(1, buf, recv);

            char respond[100] = {"HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n<html><body>Hello world!<H3><H3></body></html>"};
            write(confd, respond, strlen(respond));
        }
        printf("[end] client task confd= %d\n", confd);
        if (!strncmp(buf, "exit", 4))
            close(confd);
    }

    return (void *)0;
}

int main() {
    int listenfd, confd, recv;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t client_len;
    char buf[LEN], arr[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN=16

    bzero(&serveraddr, sizeof(serveraddr)); //赋0值
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8080);
    serveraddr.sin_addr.s_addr = htons(INADDR_ANY); //(u_int32_t)0x00000000

    //第三种:创建线程池
    Task_pool *pool = pool_init();
    pthread_t tid;
    for (int i = 0; i < 8; ++i) { //假设是8核CPU
        pthread_create(&tid, NULL, pfunc, (void *)pool);
        printf("thread is: %#lx\n", tid);
    }

    //服务端第一步创建socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
        err_msg("socket");

    //第四种:epoll实现IO复用
    int epfd = epoll_create(128);
    struct epoll_event ev, events[128];
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listenfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    //第二步绑定
    if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
        err_msg("bind");
    //第三步监听
    if (listen(listenfd, 2) < 0) //客户端排队队列最多有2个
        err_msg("listen");
    //第四步accept
    printf("Accepting!\n");
    while (true) {
        int nfds = epoll_wait(epfd, events, 128, -1); //-1表示阻塞等待事件到来
        for (int i = 0; i < nfds; ++i) {
            if (events[i].data.fd == listenfd) {
                client_len = sizeof(clientaddr);
                confd = accept(listenfd, (struct sockaddr *)&clientaddr, &client_len);
                if (confd < 0)
                    err_msg("accept");
                printf("recv client ip: %s, port:%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr, arr, sizeof(arr)), ntohs(clientaddr.sin_port));

                //监听事件
                ev.events = EPOLLIN | EPOLLET; //事件到来或边缘触发
                ev.data.fd = confd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, confd, &ev);
            } else if (events[i].events & EPOLLIN) { //发生事件:有数据到来
                int clifd = events[i].data.fd;
                if (clifd < 3) continue;
                pool_push(pool, clifd); //客户入队*/
            }
        }
    }

    pool_free(pool);
    return 0;
}

4.2 TCP客户端

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

int main() {
    char buf[100] = {"Hello!"};

    struct sockaddr_in serveraddr;
    bzero(&serveraddr, sizeof(serveraddr)); //赋0值
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);

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

    connect(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    int recv;
    while (recv = read(0, buf, 100)) {     
        write(sock, buf, recv);
        if (!strncmp(buf, "exit", 4))
            break;
        recv = read(sock, buf, 100);
        printf("recv server:\n");
        write(1, buf, recv);
    }

    close(sock);

    return 0;
}

5 UDP socket编程

服务端顺序:socket() bind() recvfrom() sendto() close()

客户端顺序:socket() sendto() recvfrom() close()

5.1 sendto() recvfrom()

#include <sys/types.h>
#include <sys/socket.h>

//发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

//接收数据
ssize_t recvfrom(int sock, void *buf, size_t len, int flags,
     struct sockaddr *from, socklen_t *fromlen);
  • sendto()的函数形参:

    sockfd:socket描述符

    buf:UDP数据报缓存区

    len:UDP数据报的长度

    flags:调用方式标志位(一般为0)

    dest_addr:接收方目标主机的结构体地址

    addrlen:dest_addr结构体的长度,一般为16


6 UDP网络通信代码示例

6.1 UDP服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define err_msg(msg) {\
    perror(msg);\
    exit(1);\
}

int main() {
    char buf[100], arr[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN=16
    socklen_t client_len;
    struct sockaddr_in server, client;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(8080);

    int sock, recv;
    sock = socket(AF_INET, SOCK_DGRAM, 0);

    bind(sock, (struct sockaddr *)&server, sizeof(server));

    printf("updserver already\n");

    while (true) {
        //阻塞等待客户端数据请求
        recv = recvfrom(sock, buf, 100, 0, (struct sockaddr *)&client, &client_len);
        if (recv < 0)
            err_msg("recvfrom");
        printf("recvfrom client ip:%s, port:%d\n", inet_ntop(AF_INET, &client.sin_addr, arr, sizeof(arr)), ntohs(client.sin_port));
	
        //发送数据给客户端
        sendto(sock, buf, recv, 0, (struct sockaddr *)&client, sizeof(client));
    }

    close(sock);
    return 0;
}

6.2 UDP客户端

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

int main() {
    char buf[100];
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
    server.sin_port = htons(8080);

    int sock, recv;
    sock = socket(AF_INET, SOCK_DGRAM, 0);

    while (recv = read(0, buf, 100)) {
        //发送数据给服务端
        recv = sendto(sock, buf, recv, 0, (struct sockaddr *)&server, sizeof(server));
	
        //阻塞等待服务端应答的数据
        recv = recvfrom(sock, buf ,100, 0, NULL, 0);
        write(1, buf, recv);
    }

    close(sock);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值