目录
1.select-TCP服务器
// 服务器端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
// 1.创建套接字
int serfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == serfd) {
perror("socket");
return -1;
}
// 2.允许端口快速重用
int reuse = 1;
if (-1 == setsockopt(serfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) {
perror("setsockopt");
return -1;
}
// 3.定义实际地址信息结构体
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET; // 地址簇
ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
ser_addr.sin_addr.s_addr = inet_addr(IP); // 本机ip, 需要转网络字节序
// 4.绑定ip和端口
if (-1 == bind(serfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
perror("bind");
return -1;
}
// 5.设置套接字为监听状态
if (-1 == listen(serfd, 10)) {
perror("listfd");
return -1;
}
// 6.定义存储成功连接客户端的地址信息
struct sockaddr_in cli_addr;
socklen_t addrlen = sizeof(cli_addr);
// 7.定义读文件描述符集合
fd_set readfds, tmpfds;
// 清空集合
FD_ZERO(&readfds);
// 添加fd到集合
FD_SET(0, &readfds);
FD_SET(serfd, &readfds);
int ret = 0; // select返回值
char buf[128] = ""; // 定义保存接收到的数据
ssize_t size = 0; // 保存每次接收到数据的大小
int clifd = -1; // 成功连接客户端的套接字
int maxfd = serfd; // 保存当前最大的fd
struct sockaddr_in saveCli[1024 - 4]; // 保存成功连接的客户端地址信息
while (1) {
tmpfds = readfds;
ret = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
if (ret < 0) {
perror("select");
return -1;
} else if (0 == ret) {
printf("时间到\n");
return -1;
}
// 监听事件
for (int i = 0; i <= maxfd; i++) {
if (!FD_ISSET(i, &tmpfds)) { // 如果不在集合中,则直接循环下一次
continue;
}
// 运行到当前位置,说明i在集合中
// 判断i对应文件描述符需要走的对应函数
if (0 == i) {
printf("事件触发>>>");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
printf("%s\n", buf);
} else if (serfd == i) {
clifd = accept(serfd, (struct sockaddr*)&cli_addr, &addrlen);
if (-1 == clifd) {
perror("accept");
return -1;
}
printf("clifd=%d [%s][%d]客户端连接成功\n", clifd, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
// 将成功连接的客户端地址信息保存到数据中
saveCli[clifd - 4] = cli_addr;
// 将clifd添加到读集合中
FD_SET(clifd, &readfds);
// 更新maxfd
maxfd = maxfd > clifd ? maxfd : clifd;
} else {
// 清空buf
bzero(buf, sizeof(buf));
// 接收
size = recv(i, buf, sizeof(buf), 0);
if (-1 == size) {
perror("recv");
return -1;
} else if (0 == size || strcasecmp(buf, "quit") == 0) {
printf("clifd=%d [%s][%d]客户端下线\n", i, inet_ntoa(saveCli[i - 4].sin_addr), ntohs(saveCli[i - 4].sin_port));
// 关闭文件描述符
close(i);
// 从读集合中删除该文件描述符
FD_CLR(i, &readfds);
// 更新maxfd
int j = 0;
for (j = maxfd; j >= 0; j--) { // 找到当前存在集合中最大的fd
if (FD_ISSET(j, &readfds)) {
maxfd = j;
break;
}
}
if (j < 0) {
maxfd = -1;
}
continue;
}
printf("clifd=%d [%s][%d]buf=%s\n", i, inet_ntoa(saveCli[i - 4].sin_addr), ntohs(saveCli[i - 4].sin_port), buf);
strcat(buf, "*_*");
if (-1 == send(i, buf, strlen(buf), 0)) {
perror("send");
return -1;
}
}
}
}
// 关闭套接字
// close(serfd);
return 0;
}
2.select-TCP客户端
// 客户端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
// 1.创建套接字
int clifd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == clifd) {
perror("socket");
return -1;
}
// 2.定义服务器地址信息结构体
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET; // 地址簇
ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
ser_addr.sin_addr.s_addr = inet_addr(IP); // 服务器ip, 需要转网络字节序
// 3.连接服务器
if (-1 == connect(clifd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
perror("connect");
return -1;
}
// 4.定义读文件描述符集合
fd_set readfds, tmpfds;
// 添加fd到集合
FD_SET(0, &readfds);
FD_SET(clifd, &readfds);
int ret = 0; // select返回值
char buf[128] = ""; // 定义保存接收到的数据
ssize_t size = 0; // 保存每次接收到数据的大小
// 5.监听文件描述符集合
while (1) {
tmpfds = readfds; // 保存集合
// 阻塞监听
ret = select(clifd + 1, &tmpfds, NULL, NULL, NULL);
if (ret < 0) {
perror("select");
return -1;
} else if (ret == 0) {
printf("时间到\n");
return -1;
}
// 判断触发的文件描述符
if (FD_ISSET(0, &tmpfds)) {
// 发送
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if (-1 == send(clifd, buf, strlen(buf), 0)) {
perror("send");
return -1;
}
}
// 判断触发的文件描述符
if (FD_ISSET(clifd, &tmpfds)) {
// 清空buf
bzero(buf, sizeof(buf));
// 接收
size = recv(clifd, buf, sizeof(buf), 0);
if (-1 == size) {
perror("recv");
return -1;
} else if (0 == size) {
printf("[%s][%d]服务器下线\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port));
break;
}
printf("[%s][%d]buf=%s\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port), buf);
}
}
// 关闭套接字
close(clifd);
return 0;
}
3.poll-TCP客户端
// 客户端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
// 1.创建套接字
int clifd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == clifd) {
perror("socket");
return -1;
}
// 2.定义服务器地址信息结构体
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET; // 地址簇
ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
ser_addr.sin_addr.s_addr = inet_addr(IP); // 服务器ip, 需要转网络字节序
// 3.连接服务器
if (-1 == connect(clifd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
perror("connect");
return -1;
}
// 4.定义读文件描述符集合
struct pollfd fds[2];
// 添加fd到集合
fds[0].fd = 0;
fds[0].events = POLLIN;
fds[1].fd = clifd;
fds[1].events = POLLIN;
int ret = -1; // select返回值
char buf[128] = ""; // 定义保存接收到的数据
ssize_t size = 0; // 保存每次接收到数据的大小
// 5.监听文件描述符集合
while (1) {
// 阻塞监听
ret = poll(fds, 2, -1);
if (ret < 0) {
perror("poll");
return -1;
} else if (ret == 0) {
printf("时间到\n");
return -1;
}
// 判断触发的POLLIN
if (fds[0].revents & POLLIN) {
// 发送
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if (-1 == send(clifd, buf, strlen(buf), 0)) {
perror("send");
return -1;
}
}
// 判断触发的POLLIN
if (fds[1].revents & POLLIN) {
// 清空buf
bzero(buf, sizeof(buf));
// 接收
size = recv(clifd, buf, sizeof(buf), 0);
if (-1 == size) {
perror("recv");
return -1;
} else if (0 == size) {
printf("[%s][%d]服务器下线\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port));
break;
}
printf("[%s][%d]buf=%s\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port), buf);
}
}
// 关闭套接字
close(clifd);
return 0;
}
4.基于UDP上传下载客户端
// 客户端(文件上传下载)
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// 服务器端口号
#define POST 69
// 服务器ip
// #define IP "192.168.2.140" // 使用命令行传参
/// @brief 下载
/// @param serfd 服务器套接字
/// @param sin 地址信息
/// @return 成功0 失败-1
int download(int serfd, struct sockaddr_in sin);
/// @brief 上传
/// @param serfd 服务器套接字
/// @param sin 地址信息
/// @return 成功0 失败-1
int uploading(int serfd, struct sockaddr_in sin);
int main(int argc, const char* argv[])
{
// 0.校验参数
if(argc != 2){
printf("input error\n");
printf("usage: ./a.out ip(192.168.2.140)\n");
return -1;
}
// 1.创建报式套接字
int serfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == serfd) {
perror("socket");
return -1;
}
// 2.定义服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; // 地址簇
sin.sin_port = htons(POST); // 服务器端口号, 需要本地字节序-->网络字节序
sin.sin_addr.s_addr = inet_addr(argv[1]); // 服务器ip, 需要点分十进制-->网络字节序
int options = -1; // 菜单选项
while (1) {
puts("------1.下载------");
puts("------2.上传------");
puts("------0.退出------");
scanf("%d", &options);
switch (options) {
case 0:
return 0;
case 1:
download(serfd, sin);
break;
case 2:
uploading(serfd, sin);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
}
int download(int serfd, struct sockaddr_in sin)
{
char buf[516] = { 0 }; // 数据包
// 1.初始化请求下载包
printf("input filename:");
char filename[128] = "";
scanf("%s", filename);
short* opcode = (short*)buf;
*opcode = htons(1);
sprintf(buf + 2, "%s%c%s%c", filename, '\0', "octet", '\0');
socklen_t sin_size = sizeof(sin); // 地址信息结构体大小
// 2.发送下载请求
if (-1 == sendto(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sin_size)) {
perror("sendto");
return -1;
}
ssize_t len = 0; // 保存接收的数据大小
unsigned short num = 0; // 保存数据包块编号
int filefd = -1; // 初始化文件fd
while (1) {
bzero(buf, sizeof(buf));
// 3.接收数据
len = recvfrom(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_size);
if (-1 == len) {
perror("recvfrom");
return -1;
}
// 校验数据包的操作码是否==3
if (3 == buf[1]) {
// 校验数据包的块编号是否与本地记录一致
if (*(unsigned short*)(buf + 2) == htons(num + 1)) {
num++;
// 校验数据包块编号是否==1
if (1 == ntohs(*(unsigned short*)(buf + 2))) {
filefd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (-1 == filefd) {
perror("open");
return -1;
}
}
// 保存数据到本地
if (-1 == write(filefd, buf + 4, len - 4)) {
perror("write");
close(filefd);
return -1;
}
// 回复ACK
buf[1] = 4;
if (-1 == sendto(serfd, buf, 4, 0, (struct sockaddr*)&sin, sin_size)) {
perror("recvfrom");
close(filefd);
return -1;
}
// 判断是否下载完毕
if (len - 4 < 512) {
printf("%s下载完毕\n", filename);
break;
}
}
} else if (5 == buf[1]) {
fprintf(stderr, "DOWNLOAD_ERROR:%d : %s\n", ntohs(*(short*)(buf + 2)), buf + 4);
return -1;
}
}
close(filefd);
return 0;
}
int uploading(int serfd, struct sockaddr_in sin)
{
char buf[516] = { 0 }; // 数据包
// 1.初始化请求上传包
printf("input filename:");
char filename[128] = "";
scanf("%s", filename);
short* opcode = (short*)buf;
*opcode = htons(2);
sprintf(buf + 2, "%s%c%s%c", filename, '\0', "octet", '\0');
socklen_t sin_size = sizeof(sin); // 地址信息结构体大小
// 2.发送上传请求
if (-1 == sendto(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sin_size)) {
perror("sendto");
return -1;
}
ssize_t len = 0; // 保存接收的数据大小
ssize_t size = 0; // 保存读取文件的数据大小
unsigned short num = 0; // 保存数据包块编号
int filefd = -1; // 初始化文件fd
while (1) {
bzero(buf, sizeof(buf));
// 3.接收ACK
len = recvfrom(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_size);
if (-1 == len) {
perror("recvfrom");
return -1;
}
// printf("操作码:%d 块编号:%d\n", buf[1], buf[3]);
// 校验ACK的操作码是否==4
if (4 == buf[1]) {
// 校验ACK的块编号是否与本地记录一致
if (*(unsigned short*)(buf + 2) == htons(num)) {
num++;
// 校验ACK块编号是否==0
if (0 == ntohs(*(unsigned short*)(buf + 2))) {
filefd = open(filename, O_RDONLY); // 打开要上传的文件
if (-1 == filefd) {
perror("open");
continue;
}
}
// 读数据到buf
size = read(filefd, buf + 4, sizeof(buf) - 4);
if (-1 == size) {
perror("read");
close(filefd);
continue;
} else if (0 == size) {
printf("%s上传完毕\n", filename);
break;
}
// 上传数据到服务器
buf[1] = 3; // 设置操作码
buf[3] = num; // 设置块编号
if (-1 == sendto(serfd, buf, size + 4, 0, (struct sockaddr*)&sin, sin_size)) {
perror("recvfrom");
close(filefd);
return -1;
}
} else { // 如果服务器回复ACK块编号错误,将文件光标移动到上次的位置
lseek(filefd, -size, SEEK_CUR);
continue;
}
} else if (5 == buf[1]) {
fprintf(stderr, "DOWNLOAD_ERROR:%d : %s\n", ntohs(*(short*)(buf + 2)), buf + 4);
return -1;
}
}
close(filefd);
return 0;
}