Linux C Socket
TCP/UDP 服务器和客户端的动作
动作 | TCP | UDP | 客户端 |
---|---|---|---|
建立套接字(socket) | Y | Y | Y |
绑定IP地址和端口号(bind) | Y | Y | Y |
监听(listen) | Y | N | N |
接受请求(accept) | Y | N | N |
连接(connect) | N | N | Y |
字节序转换
机器有大端字节序和小端字节序
而网络字节序是大端的
ntohl, ntohs
网络字节序转换为主机字节序
htonl, htons
主机字节序转换为网络字节序
网络地址转换
inet_ntoa
sockaddr_in结构体中sin_addr参数转换为IP地址字符串
inet_addr
IP地址字符串转换为sockaddr_in结构体中sin_addr参数
简单的TCP服务器
TCP需要先用accept接收客户端的套接字, 在处理完一个客户机的消息后才能处理下一个客户机的消息.
服务器端(server.c)
c code
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
#define MAXLINE 80
void if_error(int status_code, char *err_msg)
{
if (status_code < 0) {
perror(err_msg);
exit(errno);
}
}
int net_setup(int sock_type, char *ip_addr, int port, int max_connect)
{
struct sockaddr_in serv_addr;
int sock_fd, ret;
/* socket */
sock_fd = socket(AF_INET, sock_type, 0);
if_error(sock_fd, "socket");
/* bind */
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = (NULL == ip_addr ? INADDR_ANY : inet_addr(ip_addr));
ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if_error(ret, "bind");
/* TCP listen */
if (sock_type == TCP) {
ret = listen(sock_fd, max_connect);
if_error(ret, "listen");
}
return sock_fd;
}
int main(int argc, char **argv)
{
int sock_fd, acc_fd, recv_bytes;
char buf[MAXLINE];
struct sockaddr_in cli_addr = {0};
socklen_t cli_addr_len = sizeof(struct sockaddr);
/* setup tcp socket */
sock_fd = net_setup(TCP, NULL, 8080, 10);
while (1) {
/* accept */
acc_fd = accept(sock_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
if_error(acc_fd, "accept");
/* print conn msg */
printf("ip(%s), port(%d), client connect !\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
/* deal with cli msg */
while (1) {
/* recv msg */
recv_bytes = recv(acc_fd, buf, sizeof(buf), 0);
if_error(recv_bytes, "recv");
/* cli exit */
if (recv_bytes == 0) break;
/* print cli msg */
buf[recv_bytes] = '\0';
printf("from ip(%s), port(%d), client message: %s",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
}
/* close accept fd */
close(acc_fd);
/* print disconn msg */
printf("ip(%s), port(%d), client disconnect !\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
}
close(sock_fd);
return 0;
}
客户端(client.c)
c code
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
#define MAXLINE 80
void if_error(int status_code, char *err_msg)
{
if (status_code < 0) {
perror(err_msg);
exit(errno);
}
}
int net_conn(int sock_type, char *ip_addr, int port)
{
struct sockaddr_in server_addr;
int sock_fd, ret;
/* socket */
sock_fd = socket(AF_INET, sock_type, 0);
if_error(sock_fd, "socket");
/* bind */
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = (NULL == ip_addr ? INADDR_ANY : inet_addr(ip_addr));
/* connect */
ret = connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if_error(ret, "connect");
return sock_fd;
}
int main(int argc, char **argv)
{
int sock_fd, send_bytes;
char buf[MAXLINE];
/* connect tcp server */
sock_fd = net_conn(TCP, "127.0.0.1", 8080);
while (1) {
/* get msg from kbd */
fgets(buf, sizeof(buf), stdin);
if (strncmp(buf, "exit", 4) == 0) break;
/* send msg */
send_bytes = send(sock_fd, buf, sizeof(buf), 0);
if_error(send_bytes, "send");
}
close(sock_fd);
return 0;
}
UDP服务器
客户端的代码只需在连接时改成UDP, 其他不变.
sock_fd = net_conn(UDP, "127.0.0.1", 8080);
UDP不需要accept, 所以可以循环处理多个客户端的消息.
服务器端(server.c)
c code
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
#define MAXLINE 80
void if_error(int status_code, char *err_msg)
{
if (status_code < 0) {
perror(err_msg);
exit(errno);
}
}
int net_setup(int sock_type, char *ip_addr, int port, int max_connect)
{
struct sockaddr_in serv_addr;
int sock_fd, ret;
/* socket */
sock_fd = socket(AF_INET, sock_type, 0);
if_error(sock_fd, "socket");
/* bind */
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = (NULL == ip_addr ? INADDR_ANY : inet_addr(ip_addr));
ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if_error(ret, "bind");
/* TCP listen */
if (sock_type == TCP) {
ret = listen(sock_fd, max_connect);
if_error(ret, "listen");
}
return sock_fd;
}
int main(int argc, char **argv)
{
int sock_fd, recv_bytes;
char buf[MAXLINE];
struct sockaddr_in cli_addr = {0};
socklen_t cli_addr_len = sizeof(struct sockaddr);
/* setup udp socket */
sock_fd = net_setup(UDP, NULL, 8080, 0);
while (1) {
/* recv msg */
recv_bytes = recvfrom(sock_fd, buf, sizeof(buf), 0,
(struct sockaddr*)&cli_addr, &cli_addr_len);
if_error(recv_bytes, "recvfrom");
/* cli exit */
if (recv_bytes == 0) continue;
/* print cli msg */
buf[recv_bytes] = '\0';
printf("from ip(%s), port(%d), client message: %s",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
}
close(sock_fd);
return 0;
}
TCP服务器如何处理多个客户机
可以使用线程/进程来处理多个客户机的消息, 这里使用进程.
使用2次fork避免僵尸进程
客户端代码不变
服务器端(server.c)
c code
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
#define MAXLINE 80
void if_error(int status_code, char *err_msg)
{
if (status_code < 0) {
perror(err_msg);
exit(errno);
}
}
int net_setup(int sock_type, char *ip_addr, int port, int max_connect)
{
struct sockaddr_in serv_addr;
int sock_fd, ret;
/* socket */
sock_fd = socket(AF_INET, sock_type, 0);
if_error(sock_fd, "socket");
/* bind */
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = (NULL == ip_addr ? INADDR_ANY : inet_addr(ip_addr));
ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if_error(ret, "bind");
/* TCP listen */
if (sock_type == TCP) {
ret = listen(sock_fd, max_connect);
if_error(ret, "listen");
}
return sock_fd;
}
int main(int argc, char **argv)
{
int sock_fd, acc_fd, recv_bytes;
char buf[MAXLINE];
pid_t chi_pid, grand_chi_pid;
struct sockaddr_in cli_addr = {0};
socklen_t cli_addr_len = sizeof(struct sockaddr);
/* setup tcp socket */
sock_fd = net_setup(TCP, NULL, 8080, 10);
while (1) {
/* accept */
acc_fd = accept(sock_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
if_error(acc_fd, "accept");
/* print conn msg */
printf("ip(%s), port(%d), client connect !\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
/* chi process deal with cli msg */
chi_pid = fork();
if_error(chi_pid, "fork");
if (chi_pid == 0) {
/* child process */
grand_chi_pid = fork();
if_error(grand_chi_pid, "fork");
if (grand_chi_pid == 0) {
/* grand child process */
while (1) {
/* recv msg */
recv_bytes = recv(acc_fd, buf, sizeof(buf), 0);
if_error(recv_bytes, "recv");
/* cli exit */
if (recv_bytes == 0) break;
/* print msg */
buf[recv_bytes] = '\0';
printf("child pid(%d), from ip(%s), port(%d), client message: %s",
getpid(), inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
}
/* close accept fd */
close(acc_fd);
/* print disconn msg */
printf("ip(%s), port(%d), client disconnect !\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
exit(0);
}
/* chi process exit */
exit(0);
} else {
chi_pid = wait(NULL);
if_error(chi_pid, "wait");
}
}
close(sock_fd);
return 0;
}
I/O复用TCP服务器
TCP服务器利用select完成处理多个客户机
服务器端(server.c)
c code
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
#define MAXLINE 80
void if_error(int status_code, char *err_msg)
{
if (status_code < 0) {
perror(err_msg);
exit(errno);
}
}
int net_setup(int sock_type, char *ip_addr, int port, int max_connect)
{
struct sockaddr_in serv_addr;
int sock_fd, ret;
/* socket */
sock_fd = socket(AF_INET, sock_type, 0);
if_error(sock_fd, "socket");
/* bind */
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = (NULL == ip_addr ? INADDR_ANY : inet_addr(ip_addr));
ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if_error(ret, "bind");
/* TCP listen */
if (sock_type == TCP) {
ret = listen(sock_fd, max_connect);
if_error(ret, "listen");
}
return sock_fd;
}
int main(int argc, char **argv)
{
int sock_fd, acc_fd, recv_bytes;
char buf[MAXLINE];
fd_set reads, reads_init;
struct timeval timeout, timeout_init;
int i, fd_max, fd_num;
struct sockaddr_in cli_addr = {0};
socklen_t cli_addr_len = sizeof(struct sockaddr);
/* setup tcp socket */
sock_fd = net_setup(TCP, NULL, 8080, 10);
/* monitor serv socket */
FD_ZERO(&reads_init);
FD_SET(sock_fd, &reads_init);
fd_max = sock_fd;
/* set timeout time */
timeout_init.tv_sec = 5;
timeout_init.tv_usec= 0;
while (1) {
/* init reads & timeout for select */
reads = reads_init;
timeout = timeout_init;
/* select */
fd_num = select(fd_max+1, &reads, NULL, NULL, &timeout);
if_error(fd_num, "select");
/* no change */
if (fd_num == 0) {
printf("timeout!\n");
continue;
}
for (i = 0; i <= fd_max; i++) {
/* if changed */
if (FD_ISSET(i, &reads)) {
/* serv socket */
if (i == sock_fd) {
acc_fd = accept(sock_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
if_error(acc_fd, "accept");
/* add cli socket to fd set */
FD_SET(acc_fd, &reads_init);
if (fd_max < acc_fd) fd_max = acc_fd;
printf("client %d connected .\n", acc_fd);
} else {
/* cli socket, recv msg */
recv_bytes = recv(i, buf, sizeof(buf), 0);
if_error(recv_bytes, "recv");
/* remove socket from set */
if (recv_bytes == 0) {
FD_CLR(i, &reads_init);
close(i);
printf("client %d disconnected .\n", i);
} else {
buf[recv_bytes] = '\0';
printf("client %d message: %s", i, buf);
}
}
}
}
}
close(sock_fd);
return 0;
}