本文的目录导航
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;
}