一、相关函数
1、inet_ntop() //网络字节序IP转字符串
SYNOPSIS
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
==> af : 地址族 AF_INET
==> src : 需要转换的网络字节序的IP变量的地址
==> dst : 转换之后的IP存放的位置
==> size : 第三个参数的size
2、Send()
NAME
send — send a message on a socket
SYNOPSIS
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
==> socket : TCP套接字
==> buffer : 需要发送的数据
==> length : 发送的数据的大小
==> flags : 数据属性 0 (如果flags赋值0,那么send就相当于write)
3、Recv()
NAME
recv — receive a message from a connected socket
SYNOPSIS
#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);
==> socket : TCP套接字
==> buffer : 需要接收的数据存放的缓冲区
==> length : 接收的数据的大小
==> flags : 数据属性 0 (如果flags赋值0,那么recv就相当于read)
二、服务器连接多个客户端
实现一个服务器同时连接多个客户端,可以接收每一个客户端的信息。
==> 分析:
服务器在初始化套接字之后,TCP套接字会升级为监听套接字,监听套接字可以循环的被客户端连接,每次客户端连接成功,accept函数会返回一个会话ID。
==> 实现方案:
服务器初始化监听套接字之后,主线程循环的等待客户端连接,每次有一个客户端成功连接,那就创建一条线程去循环接收这个会话ID里面的内容
例子: 设计一个服务器端程序,服务器可以最多同时连接20个客户端,每次有客户端成功连接,那就打印”客户端ip[192.168.15.xx],port[xxx]连接成功!”, 然后这个客户发送信息都可以接收到。
例子1:
实现一个服务器转发信息功能。服务器循环接收客户端的连接,客户端连接成功之后可以给服务器发送数据,服务器接收到数据之后就给每一个连接的客户端进行转发。
服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
int cli_num; //当前连接的客户端的数量
int talkfd[20]; //存放客户端的会话ID
/*线程*/
void*Recv_Msg(void *arg)
{
int fd = *(int *)arg;
char buf[1024];
while(1)
{
memset(buf,0, sizeof(buf));
if(recv(fd, buf, sizeof(buf), 0) < 0)
{
break;
}
printf("recv[%d]:%s", fd, buf);
//全部转发数据(发送消息的客户端也会收到)
for(int i=0; i<cli_num; i++)
{
send(talkfd[i], buf, sizeof(buf), 0);
}
}
printf("client [%d] is disconnect!\n", fd);
//优化:使用链表存储会话ID,如果有客户端断开连接,删除这个链表节点
close(fd);
}
/*服务器转发功能 -- 服务器*/
int main(int argc, char *argv[])
{
//0, 执行程序格式 ./TCP_server <port>
if(argc != 2)
{
printf("按照格式执行程序:%s <port>\n", argv[0]);
return -1;
}
//1, 获取TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket failed");
return -1;
}
//2, bind本机IP和端口号
struct sockaddr_in server_addr, client_addr;
socklen_t addrlen = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1])); //把字符串的端口转换成网络字节序端口号
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd, (struct sockaddr *)&server_addr, addrlen) < 0)
{
perror("bind failed");
return -1;
}
//3, 设置监听套接字
listen(sockfd, 16);
//4, 循环等待客户端连接
pthread_t tid;
char ip_addr[20] = {0};
while(1)
{
if(cli_num >= 20)
{
printf("客户端已满!\n");
sleep(1);
continue;
}
talkfd[cli_num] = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if(-1 == talkfd[cli_num])
{
printf("服务器炸了!\n");
continue;
}
inet_ntop(AF_INET, &(client_addr.sin_addr), ip_addr, sizeof(ip_addr));
printf("client ip[%s], port[%d] is connect talkfd:%d\n", ip_addr, ntohs(client_addr.sin_port), talkfd[cli_num]);
pthread_create(&tid, NULL, Recv_Msg, (void *)&talkfd[cli_num]);
//==> 如果有客户端连接,那就记录会话ID,创建线程去接收信息
//==> 每一条线程循环接收数据,如果接收到数据那就转发给所有的客户端
cli_num++;
}
//5, 关闭会话套接字,关闭监听套接字
close(sockfd);
return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_t tid1, tid2;
/*读取信息*/
void *Recv_Msg(void *arg)
{
int fd = *(int *)arg;
char buf[1024];
while(1)
{
memset(buf, 0, sizeof(buf));
if( read(fd, buf, sizeof(buf)) <= 0)
{
pthread_cancel(tid2); //关闭写入线程
pthread_exit(NULL);
}
printf("readbuf:%s", buf);
if( strcmp("QUIT_TALK\n", buf) == 0 )
{
pthread_cancel(tid2); //关闭写入线程
pthread_exit(NULL);
}
}
}
/*写入信息*/
void *Send_Msg(void *arg)
{
int fd = *(int *)arg;
char buf[1024];
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(fd, buf, sizeof(buf));
if( strcmp("QUIT_TALK\n", buf) == 0 )
{
pthread_cancel(tid1); //关闭读取线程
pthread_exit(NULL);
}
}
}
/*TCP双向通信--客户端: 执行格式:./client <port> <IP>*/
int main(int argc, char *argv[])
{
//0,执行格式
if(argc != 3)
{
printf("请按照格式执行程序:%s <port> <IP>\n", argv[0]);
return -1;
}
//1,获取TCP套接字 -- socket()
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket failed");
return -1;
}
//2,连接服务器 -- connect()
struct sockaddr_in server_addr;
socklen_t addrlen = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1])); //把字符串的端口转换成网络字节序端口号
server_addr.sin_addr.s_addr = inet_addr(argv[2]); //把字符串的IP转换成网络字节序IP
//server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(connect(sockfd, (struct sockaddr *)&server_addr, addrlen) < 0 )
{
perror("connect failed");
return -1;
}
//3,创建两条线程 --> 一条线程循环读取; 一条线程循环写入
pthread_create(&tid1, NULL, Recv_Msg, (void *)&sockfd); //读信息线程
pthread_create(&tid2, NULL, Send_Msg, (void *)&sockfd); //写信息线程
//4,主线程保证不结束
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
左上为服务器,其他3为客户端,在3个终端页面运行同一个客户端代码就能产生3个客户端。
三、TCP传输控制协议实现文件传输
TCP传输协议是一个可靠的传输协议,在通信之前需要先建立连接。
1、TCP通信中的三次握手与四次分手
2、实现文件传输的步骤
1)发送方
==> 给发送方文件路径名 (判断文件是否存在,不存在就结束,如果存在,那就获取文件信息: 文件名字,大小,属性…)
==> 连接到接收方 --> connect
==> 第一个数据包包含文件的相关信息 (文件名,文件大小…)
==> 循环发送文件内容 --> 直到文件内容被发送完毕,结束。
==> 断开连接,结束程序
2)接收方
==> 初始化网络连接 --> accept();
==> 接收第一个数据包,对数据包进行解析,得到文件的相关信息
==> 在本地创建一个同名文件
==> 循环接收数据,写入新文件 (直到接收到的数据大小等于文件大小时停止接收)
==> 断开连接,结束程序。
例子2:
tcp传输文件
接收方
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
/*文件接收方*/
int main(int argc, char *argv[])
{
//1,获取TCP套接字 -- socket()
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket failed");
return -1;
}
//2,绑定自己的IP和端口号 -- bind()
struct sockaddr_in server_addr, client_addr;
socklen_t addrlen = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9999); //把字符串的端口转换成网络字节序端口号
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd, (struct sockaddr *)&server_addr, addrlen) < 0)
{
perror("bind failed");
return -1;
}
//3,设置监听套接字 -- listen()
listen(sockfd, 1);
int talkfd;
long filesize, recvsize = 0;
size_t ret;
char filename[32] = {0};
char ip_addr[20] = {0};
char MsgBuf[1024] = {0};
talkfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
inet_ntop(AF_INET, &(client_addr.sin_addr), ip_addr, sizeof(ip_addr));
printf("发送方 ip[%s], port[%d]\n", ip_addr, ntohs(client_addr.sin_port));
//5,接收第一个数据包 --> 解析文件名,文件大小
recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
sscanf(MsgBuf, "%ld:%s", &filesize, filename);
printf("接收的文件名:%s, 文件大小:%ld\n",filename, filesize);
//6,在本地创建一个同名文件 --> 打开文件
FILE *fp = fopen(filename, "w");
if(NULL == fp)
{
perror("fopen failed");
return -1;
}
//7,循环接收数据,记录接收的数据总大小 --> 如果接收的总数据大小大于等于文件大小 --> 结束
while(1)
{
memset(MsgBuf, 0, sizeof(MsgBuf));
ret = recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
fwrite(MsgBuf, 1, ret, fp);
recvsize += ret; //当前接收的文件大小
if(recvsize >= filesize)
break;
}
//8,关闭套接字,关闭文件描述符
fclose(fp);
close(talkfd);
close(sockfd);
return 0;
}
发送方
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/stat.h>
/*文件发送方*/
int main(int argc, char *argv[])
{
//0,程序执行格式 ./send_file <filename>
if(argc != 2)
{
printf("安装格式执行:%s <文件路径名>\n", argv[0]);
return -1;
}
//1,判断文件是否存在,如果存在,那就获取文件信息 (文件大小) -- stat
if( access(argv[1], F_OK) )//access函数用来判断指定的文件或目录是否存在(F_OK)
{
printf("发送的文件不存在!\n");
return -1;
}
struct stat buf;
stat(argv[1], &buf);//获取文件信息
printf("发送的文件:%s, 大小:%ld\n", argv[1], buf.st_size);
//2,获取套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket failed");
return -1;
}
//3,连接到接收方
struct sockaddr_in recv_addr;
socklen_t addrlen = sizeof(recv_addr);
recv_addr.sin_family = AF_INET;
recv_addr.sin_port = htons(9999); //把字符串的端口转换成网络字节序端口号
recv_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
if(connect(sockfd, (struct sockaddr *)&recv_addr, addrlen) < 0 )
{
perror("connect failed");
return -1;
}
//4,发送第一个数据包 文件名:文件大小
char MsgBuf[1024];
memset(MsgBuf, 0, sizeof(MsgBuf));
sprintf(MsgBuf, "%ld:%s", buf.st_size, argv[1]);
send(sockfd, MsgBuf, sizeof(MsgBuf), 0);
//5,循环发送数据 --> 循环读取文件内容 --> 循环发送 --> 发送完毕,结束
FILE *fp = fopen(argv[1], "r");
if(NULL == fp)
{
perror("fopen failed");
return 0;
}
size_t ret;
while(1)
{
ret = fread(MsgBuf, 1, sizeof(MsgBuf), fp); //从一个流中读取文件信息 ret:成功读取的元素个数
send(sockfd, MsgBuf, ret, 0); //读取的文件信息发送给对方
if(ret <= 0)
break;
}
printf("文件发送结束!\n");
//6,关闭套接字,关闭文件描述符
fclose(fp);
close(sockfd);
return 0;
}
四、UDP传输协议实现通信
1、UDP协议特点
·面向非连接 (不需要建立连接 --> 效率高)
·不可靠传输协议 (不保证数据一定正确到达)
==> 一般在网络环境不太稳定的情况下需要使用UDP协议
2、UDP通信实现流程 – 寄信
==> 例子: 设计UDP通信的发送端和接收端功能代码。
相关函数:
1)Sendto() //–> 发送一个UDP数据包
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
==> sockfd : 套接字
==> buf : 需要发送的数据内容
==> len : 需要发送的数据大小
==> flags : 数据标志 0
==> dest_addr : 对方的IP地址结构体
==> addrlen : 地址结构体大小
返回值: 成功返回实际发送的字节数,失败返回-1
2)recvform() //–> 接收数据
SYNOPSIS
#include <sys/socket.h>
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
==> sockfd : 套接字
==> buf : 存放接收的的数据内容的缓冲区
==> len : 想要接收的数据的大小
==> flags : 数据标志 0
==> dest_addr : 发送的IP地址结构体
==> addrlen : 地址结构体大小
返回值: 成功返回实际接收的字节数,失败返回-1