嵌入式Linux之基于TCP协议的程序
一、服务端(single_conn_server.c)
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define handle_error(cmd, result) \
if (result < 0) \
{ \
perror(cmd); \
return -1; \
}
void *read_from_client(void *argv)
{
int client_fd = *(int *)argv;
char *read_buf = NULL;
ssize_t count = 0;
read_buf = malloc(sizeof(char) * 1024);
if (!read_buf)
{
perror("malloc server read_buf");
return NULL;
}
while ((count = recv(client_fd, read_buf, 1024, 0)))
{
if (count < 0)
{
perror("recv");
}
fputs(read_buf, stdout);
}
printf("客户端请求关闭连接......\n");
free(read_buf);
return NULL;
}
void *write_to_client(void *argv)
{
int client_fd = *(int *)argv;
char *write_buf = NULL;
ssize_t send_count = 0;
write_buf = malloc(sizeof(char) * 1024);
if (!write_buf)
{
printf("写缓存分配失败,断开连接\n");
shutdown(client_fd, SHUT_WR);
perror("malloc server write_buf");
return NULL;
}
while (fgets(write_buf, 1024, stdin) != NULL)
{
send_count = send(client_fd, write_buf, 1024, 0);
if (send_count < 0)
{
perror("send");
}
}
printf("接收到命令行的终止信号,不再写入,关闭连接......\n");
shutdown(client_fd, SHUT_WR);
free(write_buf);
return NULL;
}
int main(int argc, char const *argv[])
{
int sockfd, temp_result, client_fd;
pthread_t pid_read, pid_write;
struct sockaddr_in server_addr, client_addr;
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
// 声明 IPV4 通信协议
server_addr.sin_family = AF_INET;
// 我们需要绑定 0.0.0.0 地址,转换成网络字节序后完成设置
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 端口随便用一个,但是不要用特权端口
server_addr.sin_port = htons(6666);
// 创建 server socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
handle_error("socket", sockfd);
// 绑定地址
temp_result = bind(sockfd, (struct sockaddr *)&server_addr,
sizeof(server_addr));
handle_error("bind", temp_result);
// 进入监听模式
temp_result = listen(sockfd, 128);
handle_error("listen", temp_result);
// 接受第一个 client 连接
socklen_t cliaddr_len = sizeof(client_addr);
client_fd = accept(sockfd, (struct sockaddr *)&client_addr,
&cliaddr_len);
handle_error("accept", client_fd);
printf("与客户端 from %s at PORT %d 文件描述符 %d 建立连接\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),
client_fd);
// 启动一个子线程,用来读取客户端数据,并打印到 stdout
pthread_create(&pid_read, NULL, read_from_client, (void *)&client_fd);
// 启动一个子线程,用来从命令行读取数据并发送到客户端
pthread_create(&pid_write, NULL, write_to_client, (void *)&client_fd);
// 阻塞主线程
pthread_join(pid_read, NULL);
pthread_join(pid_write, NULL);
printf("释放资源\n");
close(client_fd);
close(sockfd);
return 0;
}
二、客户端(single_conn_client.c)
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
// 192.168.10.150 IP 地址的 16 进制表示
#define INADDR_LOCAL 0xC0A80A96
#define handle_error(cmd, result) \
if (result < 0) \
{ \
perror(cmd); \
return -1; \
}
void *read_from_server(void *argv)
{
int sockfd = *(int *)argv;
char *read_buf = NULL;
ssize_t count = 0;
read_buf = malloc(sizeof(char) * 1024);
if (!read_buf)
{
perror("malloc client read_buf");
return NULL;
}
while (count = recv(sockfd, read_buf, 1024, 0))
{
if (count < 0)
{
perror("recv");
}
fputs(read_buf, stdout);
}
printf("收到服务端的终止信号......\n");
free(read_buf);
return NULL;
}
void *write_to_server(void *argv)
{
int sockfd = *(int *)argv;
char *write_buf = NULL;
ssize_t send_count = 0;
write_buf = malloc(sizeof(char) * 1024);
if (!write_buf)
{
printf("写缓存分配失败,断开连接\n");
shutdown(sockfd, SHUT_WR);
perror("malloc client write_buf");
return NULL;
}
while (fgets(write_buf, 1024, stdin) != NULL)
{
send_count = send(sockfd, write_buf, 1024, 0);
if (send_count < 0)
{
perror("send");
}
}
printf("接收到命令行的终止信号,不再写入,关闭连接......\n");
shutdown(sockfd, SHUT_WR);
free(write_buf);
return NULL;
}
int main(int argc, char const *argv[])
{
int sockfd, temp_result;
pthread_t pid_read, pid_write;
struct sockaddr_in server_addr, client_addr;
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
// 连接本机 127.0.0.1
server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// 连接端口 6666
server_addr.sin_port = htons(6666);
client_addr.sin_family = AF_INET;
// 连接本机 192.168.10.150
client_addr.sin_addr.s_addr = htonl(INADDR_LOCAL);
// 连接端口 8888
client_addr.sin_port = htons(8888);
// 创建 socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
handle_error("socket", sockfd);
temp_result = bind(sockfd, (struct sockaddr *)&client_addr,
sizeof(client_addr));
handle_error("bind", temp_result);
// 连接 server
temp_result = connect(sockfd, (struct sockaddr *)&server_addr,
sizeof(server_addr));
handle_error("connect", temp_result);
// 启动一个子线程,用来读取服务端数据,并打印到 stdout
pthread_create(&pid_read, NULL, read_from_server, (void *)&sockfd);
// 启动一个子线程,用来从命令行读取数据并发送到服务端
pthread_create(&pid_write, NULL, write_to_server, (void *)&sockfd);
// 阻塞主线程
pthread_join(pid_read, NULL);
pthread_join(pid_write, NULL);
printf("关闭资源\n");
close(sockfd);
return 0;
}
三、解析
在上述例程中,我们将客户端绑定到了 192.168.10.150 的 8888 端口, 192.168.10.150 实际上是本机 IP,此处等价于 localhost 或 127.0.0.1。此外,通常服务端不需要绑定到具体的 IP 和端口,如果不绑定,启动后会操作系统会随机为客户端分配本机的某个端口。我们这里将客户端绑定至指定的 IP 和端口,主要是为了在分析时便于区分客户端和服务端,实际的客户端程序完全可以省去这一步。
在 Makefile 开头补充伪目标定义和变量定义
.PHONY: single_conn single_conn_clean
single_conn_executables:=single_conn_server single_conn_client
Makefile 末尾补充目标声明
single_conn_server: single_conn_server.c
-$(CC) -o $@ $^
single_conn_client: single_conn_client.c
-$(CC) -o $@ $^
single_conn: $(single_conn_executables)
single_conn_clean:
-rm ./$(word 1, $(single_conn_executables)) ./$(word 2,
$(single_conn_executables))
通过 Makefile 编译后,打开两个命令窗口,执行两个端,即可实现数据互发!。