Linux-C TCP应用案例
TCP相关API的介绍: TCP相关API介绍.
本文代码下载地址: tcp案例集合.
案例1.TCP实现读与写
1.write.c(即client)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
//./client 192.168.33.3
int main(int argc, const char *argv[])
{
int skt_fd;
int retval;
char buffer[1024];
ssize_t send_size;
struct sockaddr_in srv_addr;
//步骤1:获取程序通信的套接字(接口)
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
goto apply_socket_err;
}
//步骤2:绑定IP地址
srv_addr.sin_family = AF_INET;//指定引用IPV4的协议
srv_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
srv_addr.sin_addr.s_addr = inet_addr(argv[1]);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定
//步骤3:发起连接服务器的请求
retval = connect(skt_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
if(retval == -1)
{
perror("客户端连接到服务器失败\n");
goto connect_server_err;
}
printf("客户端:连接服务器成功\n");
//发送信息
while(1)
{
scanf("%s", buffer);
send_size = send( skt_fd, buffer, strlen(buffer), 0);
if(send_size == -1)
break;
}
close(skt_fd);
return 0;
apply_socket_err:
connect_server_err:
close(skt_fd);
return -1;
}
2.read.c(即server)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
int main(void)
{
int skt_fd; //服务端文件描述符
int retval; //判断返回值
char buffer[1024];
ssize_t recv_size;
struct sockaddr_in native_addr;
int client_fd; //客户端文件描述符
struct sockaddr_in client_addr;
socklen_t sklen = sizeof(client_addr);
//步骤1:获取程序通信的套接字(接口)
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
goto apply_socket_err;
}
//步骤2:绑定IP地址
native_addr.sin_family = AF_INET;//指定引用IPV4的协议
native_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
native_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定
//步骤3:将指定的地址信息及本程序的套接字绑定在一起
retval = bind( skt_fd, (struct sockaddr *)&native_addr, sizeof(native_addr));
if(retval == -1)
{
perror("绑定套接字地址失败");
goto bind_socket_err;
}
//步骤4:设置监听,最多可连接50个客户端
retval = listen(skt_fd, 50);
if(retval == -1)
{
perror("设置最大连接数失败");
goto set_listen_err;
}
//步骤5:等待客户端链接,链接成功后返回一个代表客户端通信的文件描述符,具备阻塞特性
client_fd = accept(skt_fd, (struct sockaddr *)&client_addr, &sklen);
if(client_fd == -1)
{
perror("客户端链接失败");
goto client_connect_err;
}
printf("服务器:客户端连接成功\n");
printf("客户端信息:\n客户端IP为%s,端口号为%hu\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
//接收消息
while(1)
{
bzero(buffer, sizeof(buffer));
//接受来自与客户端的数据,这个接受具备阻塞特性,同read
recv_size = recv(client_fd, buffer, sizeof(buffer), 0);
if(recv_size == -1)
{
perror("接受数据异常");
goto socket_recv_err;
}
else if(recv_size == 0)//代表客户端断开连接
break;
printf("接收到来自与客户端%ld个字节的数据:%s\n", recv_size, buffer);
}
close(client_fd);//关闭客户端通信
close(skt_fd);//关闭服务器的socket资源
return 0;
socket_recv_err:
close(client_fd);
apply_socket_err:
client_connect_err:
set_listen_err:
bind_socket_err:
close(skt_fd);
return -1;
}
案例2.TCP多线程实现双向通信
1.server.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
struct msg_buf{
char name[16];
char buf[1024];
};
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
struct msg_buf msg;
int client_fd = *(int *)arg;//通信的socket
ssize_t recv_size;
while(1)
{
//char buf[1024];
bzero(&msg, sizeof(msg));
//recv_size = recv(client_fd, &msg, sizeof(msg), 0);
recv_size = read(client_fd, &msg, sizeof(msg));
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
else if(strncmp(msg.buf, "exit", 4) == 0 || strcmp(msg.buf, "") == 0)
{
break;//退出
}
printf("%s的新的消息:\n%s\n", msg.name, msg.buf);
}
return NULL;
}
// ./server 名字
int main(int argc, char **argv)
{
int retval;
int socket_fd;
struct msg_buf msg;
struct sockaddr_in client_addr;
int len = sizeof(client_addr);
int client_fd;
pthread_t pid;
size_t send_size;
//1 创建tcp通信socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("申请套接字失败!\n");
goto get_socket_fd_err;
}
//2 绑定socket地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;//指定引用IPV4的协议
server_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//指定端口号,转化为网络字节序(大端序)
//3 将指定的地址信息及本程序的套接字绑定在一起
retval = bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(retval == -1)
{
perror("绑定套接字地址失败!\n");
goto bind_socket_err;
}
//4 设置监听,最多可连接50个客户端
retval = listen(socket_fd, 50);
if(retval == -1)
{
perror("设置最大连接数失败!\n");
goto listen_client_err;
}
//5 等待客户端连接
client_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(client_fd == -1)
{
perror("客户端链接失败!\n");
goto accept_client_err;
}
printf("服务器:客户端连接成功,开始聊天,输入‘exit’退出聊天\n");
printf("客户端IP为%s,端口号为%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
retval = pthread_create(&pid, NULL, recv_msg, (void*)&client_fd);
if(retval != 0)
{
printf("开启线程失败\n");
goto creat_pthread_err;
}
strcpy (msg.name, argv[1]);
while(1)
{
scanf("%s", msg.buf);
//send_size = send( client_fd, buf, strlen(buf), 0);
send_size = write(client_fd, &msg, sizeof(msg));//发送消息
if(strcmp(msg.buf, "exit") == 0 || strcmp(msg.buf, "") == 0)
{
retval = pthread_cancel(pid);//取消线程
break;
}
}
//5 关闭通信socket
close(client_fd);
close(socket_fd);
return 0;
accept_client_err:
creat_pthread_err:
listen_client_err:
bind_socket_err:
get_socket_fd_err:
return -1;
}
2.client.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
struct msg_buf{
char name[16];
char buf[1024];
};
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
struct msg_buf msg;
int client_fd = *(int *)arg;//通信的socket
ssize_t recv_size;
char buf[1024];
while(1)
{
bzero(&msg, sizeof(msg));
//recv_size = recv(client_fd, &msg, sizeof(msg), 0);
recv_size = read(client_fd, &msg, sizeof(msg));
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
else if(strncmp(msg.buf, "exit", 4) == 0 || strcmp(msg.buf, "") == 0)
{
break;//退出
}
printf("%s的新的消息:\n %s\n", msg.name, msg.buf);
}
return NULL;
}
// ./server IP 名字
int main(int argc, char **argv)
{
int retval;
int socket_fd;
struct msg_buf msg;
struct sockaddr_in client_addr;
int len = sizeof(client_addr);
char buf[1024];
pthread_t pid;
size_t send_size;
//1 创建tcp通信socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("申请套接字失败!\n");
goto get_socket_fd_err;
}
//2 绑定socket地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;//指定引用IPV4的协议
server_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//指定端口号,转化为网络字节序(大端序)
//3:发起连接服务器的请求
retval = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(retval == -1)
{
perror("客户端连接到服务器失败\n");
goto connect_server_err;
}
printf("客户端:连接服务器成功,开始聊天,输入‘exit’退出聊天\n");
retval = pthread_create(&pid, NULL, recv_msg, (void*)&socket_fd);
if(retval != 0)
{
printf("开启线程失败\n");
goto creat_pthread_err;
}
strcpy (msg.name, argv[2]);
while(1)
{
scanf("%s", msg.buf);
//send_size = send( socket_fd, buf, strlen(buf), 0);
send_size = write(socket_fd, &msg, sizeof(msg));//发送消息
if(strcmp(msg.buf, "exit") == 0 || strcmp(msg.buf, "") == 0)
{
retval = pthread_cancel(pid);//取消线程
break;
}
}
//5 关闭通信socket
//close(client_fd);
close(socket_fd);
return 0;
get_socket_fd_err:
creat_pthread_err:
connect_server_err:
return -1;
}
案例3.TCP多路复用监控多个文件描述符
select.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
int main(void)
{
int skt_fd;
int retval;
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}
struct sockaddr_in native_addr;
native_addr.sin_family = AF_INET;//指定引用IPV4的协议
native_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
native_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定
retval = bind( skt_fd, (struct sockaddr *)&native_addr, sizeof(native_addr));
if(retval == -1)
{
perror("绑定套接字地址失败");
goto bind_socket_err;
}
retval = listen(skt_fd, 50);
if(retval == -1)
{
perror("设置最大连接数失败");
goto set_listen_err;
}
int max_fd = skt_fd;//拿到要操作的文件描述符中最大文件描述符
fd_set read_fd_set;//读文件描述符集合
int client_fd;
struct sockaddr_in client_addr;
socklen_t sklen = sizeof(client_addr);
printf("start listen network\n");
char buffer[1024];
while(1)
{
FD_ZERO(&read_fd_set);//清空一下文件描述符集合
FD_SET(skt_fd, &read_fd_set);
FD_SET( 0, &read_fd_set);
/*最新创建的文件描述符即为最大文件描述符
max_fd+1:要监控的最大的文件描述符值+1
&read_fd_set:读文件描述符集合
NULL, NULL, NULL:分别代表写文件描述符集合,其他类型文件描述符集合,时间参数结构体
*/
retval = select(max_fd+1, &read_fd_set, NULL, NULL, NULL);//这个函数将会在这里阻塞,一直等到我们我监听的文件描述符有动静才会被唤醒
if(retval == -1)
{
perror("监听异常");
goto select_err;
}else if(retval)
{
//判断文件描述符是不是在这个集合里面,如果还存在在这个集合里面则证明是skt_fd这个文件描述符唤醒的我们
if(FD_ISSET(skt_fd, &read_fd_set))
{
client_fd = accept(skt_fd, (struct sockaddr *)&client_addr, &sklen);
if(client_fd == -1)
{
perror("客户端链接失败");
goto client_connect_err;
}
printf("服务器:客户端连接成功\n");
printf("客户端信息:\n客户端IP为%s,端口号为%hu\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
else if(FD_ISSET(0, &read_fd_set))
{
read(0, buffer, sizeof(buffer));
printf("从键盘中读取到的数据为%s\n", buffer);
}
}else
break;
}
return 0;
socket_recv_err:
close(client_fd);
client_connect_err:
select_err:
set_listen_err:
bind_socket_err:
close(skt_fd);
return -1;
}
client.c
//可使用案例二的client.c
//用法 ./client 192.168.32.111
案例4.TCP多路复用实现多人聊天室
server.c
//思路:select接收到的客户端信息插入链表,遍历链表打印收到的信息,客户端实现读写
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#define list_for_each(head, pos)\
for(pos=head->next; pos!=NULL; pos=pos->next)
typedef struct client_info{
int client_fd;
char name[256];
char ip[16];
short port;
pthread_t tid;
struct client_info *next;
}client_info_node;
//申请客户端信息链表的头节点
static client_info_node *request_client_info_node(const client_info_node *info)
{
client_info_node *new_node;
new_node = malloc(sizeof(client_info_node));
if(new_node == NULL)
{
perror("申请客户端节点异常");
return NULL;
}
if(info != NULL)
*new_node = *info;
new_node->next = NULL;
return new_node;
}
static inline void insert_client_info_node_to_link_list(client_info_node *head, client_info_node *insert_node)
{
client_info_node *pos;
for(pos=head; pos->next != NULL; pos=pos->next);
pos->next = insert_node;
}
void *client_thread(void *arg)
{
int client_fd;
char buffer[1024];
ssize_t recv_size, send_size;
client_info_node *pos, *list_head = arg;
list_for_each(list_head, pos)
{
if(pthread_self() == pos->tid)
{
client_fd = pos->client_fd;
break;
}
}
while(1)
{
bzero(buffer, sizeof(buffer));
recv_size = recv(client_fd, buffer, sizeof(buffer), 0);
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
else if(recv_size == 0)//代表客户端断开连接
{
//删除链表节点
break;
}
list_for_each(list_head, pos)
{
if(pos->client_fd == client_fd)
continue;
printf("转发数据给端口号为%hu\n", pos->port);
send_size = send( pos->client_fd, buffer, recv_size, 0);
if(send_size == -1)
break;
}
printf("接收到来自与客户端%ld个字节的数据:%s\n", recv_size, buffer);
}
return NULL;
}
int main(void)
{
int skt_fd;
int retval;
client_info_node *list_head, *new_node;
client_info_node cache_client_info;
list_head = request_client_info_node(NULL);
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}
struct sockaddr_in native_addr;
native_addr.sin_family = AF_INET;//指定引用IPV4的协议
native_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)
native_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定
retval = bind( skt_fd, (struct sockaddr *)&native_addr, sizeof(native_addr));
if(retval == -1)
{
perror("绑定套接字地址失败");
goto bind_socket_err;
}
/*
设置套接字的同时通信最大连接数为50,并且将这个套接字的属性设置为可监听属性
*/
retval = listen(skt_fd, 50);
if(retval == -1)
{
perror("设置最大连接数失败");
goto set_listen_err;
}
int client_fd;
struct sockaddr_in client_addr;
socklen_t sklen = sizeof(client_addr);
while(1)
{
cache_client_info.client_fd = accept(skt_fd, (struct sockaddr *)&client_addr, &sklen);
if(cache_client_info.client_fd == -1)
{
perror("客户端链接失败");
goto client_connect_err;
}
strcpy(cache_client_info.ip, inet_ntoa(client_addr.sin_addr));//存放IP地址
cache_client_info.port = ntohs(client_addr.sin_port);
//新建节点
new_node = request_client_info_node(&cache_client_info);
//将节点插入链表
insert_client_info_node_to_link_list(list_head, new_node);
printf("服务器:客户端连接成功\n");
printf("客户端信息:\n客户端IP为%s,端口号为%hu\n", cache_client_info.ip, cache_client_info.port);
pthread_create(&(new_node->tid), NULL, client_thread, list_head);
}
close(client_fd);//关闭客户端通信
close(skt_fd);//关闭服务器的socket资源
return 0;
socket_recv_err:
close(client_fd);
client_connect_err:
set_listen_err:
bind_socket_err:
close(skt_fd);
return -1;
}
client.c
//可用案例二client.c
//用法 ./client 192.168.32.111
案例5.TCP服务端向客户端发送文件
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
//./server
int main(void)
{
int skt_fd;
int retval;
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}
struct sockaddr_in native_addr;
native_addr.sin_family = AF_INET;//指定引用IPV4的协议
native_addr.sin_port = htons(7777);//指定端口号,转化为网络字节序(大端序)
native_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定
retval = bind( skt_fd, (struct sockaddr *)&native_addr, sizeof(native_addr));
if(retval == -1)
{
perror("绑定套接字地址失败");
goto bind_socket_err;
}
retval = listen(skt_fd, 1);
if(retval == -1)
{
perror("设置最大连接数失败");
goto set_listen_err;
}
int client_fd;
struct sockaddr_in client_addr;
socklen_t sklen = sizeof(client_addr);
client_fd = accept(skt_fd, (struct sockaddr *)&client_addr, &sklen);
if(client_fd == -1)
{
perror("客户端链接失败");
goto client_connect_err;
}
printf("服务器:客户端连接成功\n");
//printf("客户端信息:\n客户端IP为%s,端口号为%hu\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
char buffer[1024];
ssize_t recv_size;
bzero(buffer, 1024);
// 然后从buffer(缓冲区)拷贝到file_name中
char file_name[512+1];
bzero(file_name, 512+1);
printf("请输入一个文件名字:\t");
scanf("%s", file_name);
bzero(buffer, sizeof(buffer));
strncpy(buffer, file_name, strlen(file_name)>1024?1024:strlen(file_name));
// 向服务器发送buffer中的数据
if(send(client_fd, buffer, 1024, 0) < 0)
{
perror("文件写入失败:");
return -1;
}
// 打开文件并读取文件数据
FILE *fp = fopen(file_name, "r");
if(NULL == fp)
{
printf("文件%s 不存在\n", file_name);
return -1;
}
else
{
bzero(buffer, 1024);
int length = 0;
// 读取1024数据,将其发送给客户端,循环直到文件读完为止
while((length = fread(buffer, sizeof(char), 1024, fp)) > 0)
{
if(send(client_fd, buffer, length, 0) < 0)
{
printf("发送文件%s 失败./n", file_name);
break;
}
bzero(buffer, 1024);
}
// 关闭文件
fclose(fp);
printf("文件%s 传输失败!\n", file_name);
}
close(skt_fd);
close(client_fd);
return 0;
socket_recv_err:
close(client_fd);
client_connect_err:
set_listen_err:
bind_socket_err:
close(skt_fd);
return -1;
}
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
//./client
int main(int argc, const char *argv[])
{
int skt_fd;
int retval;
/*
获取程序通信的套接字(接口)
AF_INET:IPV4的协议
SOCK_STREAM:指定TCP协议
0:代表不变化协议内部(ip手册中指定的参数)
*/
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;//指定引用IPV4的协议
srv_addr.sin_port = htons(7777);//指定端口号,转化为网络字节序(大端序)
//将所有的IP地址转化为二进制的网络字节序的数据进行绑定,根据项目的需求传入所需的IP
srv_addr.sin_addr.s_addr = inet_addr("192.168.32.116");
retval = connect(skt_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
if(retval == -1)
{
perror("客户端连接到服务器失败\n");
goto connect_server_err;
}
printf("客户端:连接服务器成功\n");
char buffer[1024];
char file_name[1024+1];
bzero(file_name, 1024+1);
if(recv(skt_fd, buffer, 1024, 0) < 0)
{
perror("Server Recieve Data Failed:");
return -1;
}
strncpy(file_name, buffer, strlen(buffer)>512?512:strlen(buffer));
printf("%s\n", file_name);
// 打开文件,准备写入
FILE *fp = fopen(file_name, "w");
if(NULL == fp)
{
perror("无法打开文件\n");
return -1;
}
// 从服务器接收数据到buffer中
// 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止
bzero(buffer, 1024);
int length = 0;
while((length = recv(skt_fd, buffer, 1024, 0)) > 0)
{
if(fwrite(buffer, sizeof(char), length, fp) < length)
{
perror("写入失败\n");
break;
}
bzero(buffer, 1024);
}
// 接收成功后,关闭文件,关闭socket
printf("接受文件:\t%s 成功!\n", file_name);
fclose(fp);
close(skt_fd);
return 0;
connect_server_err:
close(skt_fd);
return -1;
}