基於TCP(面向鏈接)的socket編程
Server端的流程如下:
-
1 創建Socket套接字
-
2 將套接字綁定到一個本地地址(IP)和端口(port)上
-
3 將套接字設置爲監聽模式,準備接收客戶端請求(listen)
-
4 等待客戶端請求,當請求到來後,接受鏈接請求,返回一個新的對應於此次鏈接的套接字(accept)
-
5 用返回的套接字和客戶端進行通信(send/recv read/write)
-
6 返回,等待另一個客戶請求
-
7 關閉套接字
/*
*tcp_server.c
*/
#include "tcp-server.h"
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
static const size_t MAX_FRAME_SIZE = 4100;
static const int PORT = 1234;
static device_t tcp_device;
static int listen_fd = -1;
static int tcp_server_init(device_t *dev) {
struct sockaddr_in addr;
//創建socket,獲取一個socket描述符
if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "%s(): cannot create socket", __FUNCTION__);
return -1;
}
//設置server的地址同通信協議
memset(&addr, 0, sizeof(struct sockaddr_in));
//協議族: AF_INET AF_INET6 AF_LOCAL
addr.sin_family = AF_INET;
//取本地任意一個地址進行通信
// uint32_t htonl(uint32_t hostlong) 將主機無符號長整型轉化爲網絡字節
//INADDR_ANY=0.0.0.0
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// uint16_t htons(uint16_t hostshort) 將hostshort 轉化爲網絡字節序
//網絡字節採用大端模式(big-ending),高位存放在低地址
addr.sin_port = htons(PORT);
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
fprintf(stderr, "%s(): bind() failed", __FUNCTION__);
goto failed;
}
if (listen(listen_fd, LISTEN_BACKLOG) != 0) {
fprintf(stderr, "%s(): listen() failed", __FUNCTION__);
goto failed;
}
fprintf(stderr, "%s(): tcp_server running on %s port=%d\n", __FUNCTION__, inet_ntoa(addr.sin_addr), PORT);
dev->recv_buf = (unsigned char *)malloc(MAX_FRAME_SIZE);
dev->recv_cnt = 0;
if (dev->recv_buf == NULL) {
fprintf(stderr, "%s(): not enough memory", __FUNCTION__);
goto failed;
}
dev->dev_send = write;
return 0;
failed:
close(listen_fd);
listen_fd = -1;
return -1;
}
static int tcp_server_main(int argc, char **argv) {
//使用fd_set以及select實現非阻塞模式
fd_set rfds, afds;
int max_fd = 0, fd;
int i;
struct sigaction sa;
//清空文件描述符集合
FD_ZERO(&afds);
if (listen_fd != -1) {
FD_SET(listen_fd, &afds);
max_fd = MAX(max_fd, listen_fd);
}
if (max_fd == 0) {
fprintf(stderr, "%s(): terminated\n", __FUNCTION__);
return -1;
}
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = tcp_server_handle_sighup;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) == -1) {
fprintf(stderr, "%s(): sigaction() error: %d", __FUNCTION__, errno);
return -1;
}
while (1) {
rfds = afds; /* structure assignment */
//監聽所有文件描述符
if (select(max_fd + 1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "%s(): select() error: %d", __FUNCTION__, errno);
break;
}
if (FD_ISSET(listen_fd, &rfds)) { /* client connected */
struct sockaddr_in addr;
socklen_t addr_len;
addr_len = sizeof(addr);
fd = accept(listen_fd, (struct sockaddr *)&addr, &addr_len);
if (fd != -1) {
//把建議鏈接的客戶端文件描述符添加到監聽集合
FD_SET(fd, &afds);
fprintf(stderr, "%s(): client %s connented\n\n", __FUNCTION__, inet_ntoa(addr.sin_addr));
max_fd = MAX(max_fd, fd);
}
}
//遍歷所有文件描述符,接收客戶端的數據
for (fd = 0; fd <= max_fd; fd++) {
if (fd == listen_fd || !FD_ISSET(fd, &rfds)) {
continue;
}
//XXX:這裏把每一個客戶端的信息都存在一個結構裏面,應該爲每一個鏈接創建不同的鏈接來進行通信
tcp_device.fd = fd;
if (tcp_server_recv(&tcp_device) <= 0) {
/* client closed */
close(fd);
FD_CLR(fd, &afds);
}
}
__sleep(30L);
}
tcp_server_close(&tcp_device);
return 0;
}
int main(int argc, char **argv) {
if (tcp_server_init(&tcp_device) != 0) {
fprintf(stderr, "%s(): tcp_server_init failed!\n", __FUNCTION__);
return -1;
}
tcp_server_main(argc, argv);
return 0;
}
Client 端流程如下:
-
1 創建Socket套接字
-
2 向服務端發送鏈接請求(connect)
-
3 和服務端進行通信(send/recv)
-
4 關閉套接字
/*
*tcp_client.c
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
static int socket_fd = -1;
static int quit = 0;
static const size_t MAX_FRAME_SIZE = 4100;
static const char *tcp_server_ip = "0.0.0.0";
static const int tcp_server_port = 1234;
static void tcp_client_handle_sighup(int sig) {
//TODO:do something while recv sighup
}
static int8_t client_connet_to_server() {
struct sigaction sa;
struct sockaddr_in clientAddr;
struct sockaddr_in serverAddr;
if (socket_fd != -1) {
shutdown(socket_fd, SHUT_RDWR);
close(socket_fd);
socket_fd = -1;
}
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGPIPE, &sa, NULL) == -1) {
fprintf(stderr, "sigaction() Failed: %d", errno);
return -1;
}
bzero(&clientAddr, sizeof(clientAddr));
clientAddr.sin_family = AF_INET;
clientAddr.sin_addr.s_addr = htons(INADDR_ANY);
clientAddr.sin_port = htons(0);
//創建client
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
fprintf(stderr, "initial tcp_client create socket failed\n");
return -1;
}
// 3.將socket建立爲非阻塞,此時socket被設置爲非阻塞模式
int flags = fcntl(socket_fd, F_GETFL, 0); //獲取建立的sockfd的當前狀態(非阻塞)
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); //將當前sockfd設置爲非阻塞
if (bind(socket_fd, (struct sockaddr *)&clientAddr, sizeof(clientAddr)) == -1) {
fprintf(stderr, "initial tcp_client bind socket failed\n");
return -1;
}
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(tcp_server_ip);
serverAddr.sin_port = htons(tcp_server_port);
while (connect(socket_fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) {
static int8_t circleTime = 0;
++circleTime;
if (circleTime >= 20) {
circleTime = 0;
}
if (quit) {
fprintf(stderr, "tcp client thread terminated\n");
return -1;
}
sleep(2);
fprintf(stderr, "start to connect the host:%s\n", tcp_server_ip);
}
fprintf(stderr, "connect the host:%s success\n", tcp_server_ip);
return 0;
}
int main(int argc, char **argv) {
char *buf = "client test";
char recv_buf[MAX_FRAME_SIZE];
ssize_t recv_len = 0;
int i;
if (client_connet_to_server() == 0) {
while (1) {
if (write(socket_fd, buf, strlen(buf)) <= 0) {
fprintf(stderr, "send %s to server failed!\n", buf);
}
recv_len = read(socket_fd, recv_buf, MAX_FRAME_SIZE);
if (recv_len > 0) {
//TODO:code the protocol recv here(may have start or end flag)
fprintf(stderr, "/**************************/\n");
fprintf(stderr, "%s\n ", recv_buf);
fprintf(stderr, "/**************************/\n\n");
}
sleep(5);
}
};
}