C语言网络编程 -- TCP/iP协议

一、Socket简介

1.1 什么是socket

        socket通常也称作"套接字",⽤于描述IP地址和端⼝,是⼀个通信链的句柄,应⽤
程序通常通过"套接字"向⽹络发出请求或者应答⽹络请求。⽹络通信就是两个进程
间的通信,这两个进程之间是如何识别彼此的呢?那就是套接字(Socket),每个
套接字由⼀个 IP 地址和⼀个端⼝号组成。Socket起源于Unix,⽽Unix/Linux基本
哲学之⼀就是“⼀切皆⽂件”,对于⽂件⽤【打开】【读写】【关闭】模式来操作
socket是⼀种特殊的⽂件,⼀些socket函数就是对其进⾏的操作(打开、读/写
IO、关闭)。

套接字的类型:
1.流式套接字(SOCK_STREAM)
        提供了⼀个⾯向连接、可靠的数据传输服务,数据⽆差错、⽆重复的发送且按发
送顺序接收。内设置流量控制,避免数据流   淹没慢的接收⽅。数据被看作是字节
流,⽆⻓度限制。
2. 数据报套接字(SOCK_DGRAM)
        提供⽆连接服务。数据包以独⽴数据包的形式被发送,不提供⽆差错保证,数
据可能丢失或重复,顺序发送,可能乱序接收。
3. 原始套接字(SOCK_RAW)
        可以对较低层次协议,如IP、ICMP直接访问。功能强⼤但使⽤较为不便, 主要⽤于
⼀些协议的开发. socket在所有的⽹络操作系统中是必不可少,它是⽹络通信中应⽤程序对应的进程和⽹络协议之间的接⼝。具体套接字在⽹络系统中地位如下:

 1.2 网络字节序

        计算机数据存储有两种字节优先顺序:⾼位字节优先低位字节优先。Internet上数据以⾼位字节优先顺序 在⽹络上传输,所以对于在内部是以低位字节优先⽅式存储数据的机器,在Internet上传输数据时就需要 进⾏转换,否则就会出现数据不⼀致。
        内存中存储的多字节数据相对于内存地址有⼤端和⼩端之分,⽹络数据流同样有⼤端⼩端之分。
主机字节序:就是⾃⼰的主机内部,内存中数据的存放⽅式,可以分为两种:  
        1.⼤端字节序(big-endian):按照内存的增⻓⽅向,⾼位数据存储于低位内存中
        2.⼩端字节序(little-endian):按照内存的增⻓⽅向,⾼位数据存储于⾼位内存中。
⼤多数Intel兼容机都采⽤⼩端模式。

 

 

//写代码判断当前是⼤端机还是⼩端机
//UN是⼀个联合体,所有变量公⽤⼀块内存,在内存中的存储是按最⻓的那个变量所需要的位数来开辟内存的。
#include<iostream>
using namespace std;
union UN{
 char ch;
 int data;
};
int main()
{
 union UN un;
 un.data = 0x1a2b3c4d;
 if(un.ch == 0x4d)
 printf( "这是⼀个⼩端机"); //在x86平台上线读取低位再读取⾼位地址数据
 else if(un.ch == 0x1a)
 printf("这是⼀个⼤端机");
 else
 printf("⽆法判定该机器" );
 return 0;
}
        网络字节顺序: 是TCP/IP中规定好的⼀种数据表示格式,它与具体的CPU类型、操作系统等⽆关,⽹络数据流同样有⼤端⼩端之分 ,也就是说,当接收端收到第⼀个字节的时候,它将这个字节作为⾼位字节还是低位字节处理. 从⽽可以保证数据在不同主机之间传输时能够被正确解释。⽹络字节顺序采⽤big endian排序⽅式(⾼位字节优先)。
        ⼀般考虑到计算机中的字符与⽹络中的字符存储顺序是不同的,当计算机中的整
数与⽹络中的整数进⾏交换时,需要相关的函数进⾏转换。
#include <arpa/inet.h>
/*主机字节顺序 --> ⽹络字节顺序*/

● uint32_t htonl(uint32_t hostlong); /* IP*/
● uint16_t htons(uint16_t hostshort); /* 端⼝*/

in_addr_t inet_addr(const char *cp); //将⼀个点分字符串IP地址转换为⼀个32位的
⽹络序列IP地址。所属头⽂件:Winsock2.h (windows) arpa/inet.h (Linux)


/*⽹络字节顺序 --> 主机字节顺序*/
● uint32_t ntohl(uint32_t netlong); /* IP*/
● char *inet_ntoa(struct in_addr in);//将⼀个32位的⽹络字节序转换为⼀个点分⼗进制字符串

struct in_addr //结构体in_addr ⽤来表示⼀个32位的IPv4地址。
{
    in_addr_t s_addr; //in_addr_t ⼀般为 32位的unsigned int,其字节顺序为⽹络顺序
};

⼆、基于TCP/IP协议的Socket通信

 2.1 基于TCP/ip的相关通信api简介

 三、 TCP协议通信流程

服务器建立步骤:

1. 创建套接字

        创建套接字,会创建⼀个结构体及收发缓冲区。此时并不指定该套接字在哪个IP和PORT⼝上, listen() — ⽤于为侦听端⼝创建两个队列⽤于接收客户端的SYN请求 bind将socket绑定在特定的IP/PORT⼝上 accept将侦听端⼝中的ESTABLISHED队列中取出那些连接。
#include <sys/types.h> /* See NOTES */  //需要包含的头文件
#include <sys/socket.h>
 
 //建⽴⼀个新的socket(即为建⽴⼀个通信端⼝)
 int socket(int domain, int type, int protocol);
 成功返回⾮负的套接字描述符,失败返回 -1
 
 参数说明:
 domain:即协议域,⼜称为协议族(family)
 Name                     Purpose                              Man page
 AF_UNIX, AF_LOCAL      Local communication                     unix(7)
 AF_INET                IPv4 Internet protocols                  ip(7)
 AF_INET6               IPv6 Internet protocols                 ipv6(7)
 AF_IPX                 IPX - Novell protocols
 AF_NETLINK             Kernel user interface device            netlink(7)
 AF_X25                 ITU-T X.25 / ISO-8208 protocol            x25(7)
 AF_AX25                Amateur radio AX.25 protocol
 AF_ATMPVC              Access to raw ATM PVCs
 AF_APPLETALK           AppleTalk                                ddp(7)
 AF_PACKET Low level packet interface packet(7)
 AF_ALG                 Interface to kernel crypto API
 type:
     SOCK_STREAM TCP
     SOCK_DGRAM UDP
     SOCK_SEQPACKET 为最⼤⻓度固定的数据报提供有序、可靠、基于双向连接的数据
传输路径:
     SOCK_RAW 原始套接字
     SOCK_RDM 提供不保证排序的可靠数据报层。
 
 protocol:
     ⽤于指定socket所使⽤的传输协议编号,通常默认设置为0即可
     0选择type类型对应的默认协议;
     IPPROTO_TCP:TCP传输协议;
     IPPROTO_UDP:UDP传输协议;

2. 绑定套接字和服务器地址

        注意:⼀般来说,⼀个端⼝释放后会等待两分钟之后才能再被使⽤,
SO_REUSEADDR是让端⼝释放后⽴即就可以被再次使用。SO_REUSEADDR⽤于
对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使⽤。server程序
总是应该在调⽤bind()之前设置SO_REUSEADDR套接字选项。TCP,先调⽤
close()的⼀⽅会进⼊TIME_WAIT状态

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
 //⽤来给参数sockfd的socket设置⼀个名称,该名称由addr参数指向的sockadr结构
 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen
);

 返回说明:成功返回 0 失败返回 -1
 ⽤途:主要⽤与在TCP中的连接
 
 形参说明:
 sockfd 套接字⽂件描述符
 addr 服务器地址信息
     struct sockaddr {
         sa_family_t sa_family;
         char sa_data[14];
     }
 
 但在编程中⼀般使⽤下边这种等价结构sockaddr,对于IPV4我们常⽤这个结构
 注意:使⽤该结构需要包含:#include <netinet/in.h>头⽂件 ****
     struct sockaddr_in {
         sa_family_t sin_family; IPV4对应AF_INET
         //htons()
         u_int16_t sin_port; 端⼝号//sin_port存储端⼝号(使⽤⽹络字节顺序)
         struct in_addr sin_addr; IP地址 //inet_addr()将字符串形象ip转⽹络字节序
 };
 
     /* Internet address. */
     struct in_addr {
         u_int32_t s_addr; IP地址
         };
 addrlen addr的⻓度 sizeof(struct sockaddr)
 
 //如果使⽤IPV6地址,需要⽤这个结构来定义变量存放ipv6相关信息 
 struct sockaddr_in6 {
     sa_family_t sin6_family; /* AF_INET6 */
     in_port_t sin6_port; /* port number */
     uint32_t sin6_flowinfo; /* IPv6 flow information */
     struct in6_addr sin6_addr; /* IPv6 address */
     uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
     };

 struct in6_addr {
    unsigned char s6_addr[16]; /* IPv6 address */
 };

3. 监听模式 

#include <sys/socket.h>
 
 //⽤于等待参数sockfd的scoket连线
 int listen(int sockfd, int backlog);
 返回值说明:成功返回0,失败返回-1
 sockfd 套接字⽂件描述符
 backlog 监听队列⻓度(等待连接的客户端的个数)缺省值20,最⼤值为128
 即为规定了内核应该为相应套接⼝排队的最⼤连接个数

4. 等待客户端连接的到来

#include <sys/types.h>
#include <sys/socket.h>
 //接收socket的连线
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen
); 
 返回值说明:成功返回连接的客户端的套接字⽂件描述符 失败返回 -1
 参数说明:
 sockfd 服务器套接字⽂件描述符
 addr 客户端信息地址,做返回值⽤的,不获取可以直接输⼊NULL
 addrlen addr的⻓度,注意是⼀个指针类型 ,传⼊指定地址的⻓度,不指定则NULL

5. 读写函数

        读(read/recvfrom/msgrcv): 读的本质来说其实不能是读,在实际中, 具体的接收数据不是由这些调⽤来进⾏,是由 于系统底层⾃动完成的。read 也好,recv 也好只负责把数据从底层缓冲copy 到我 们指定的位置.

        写的本质也不是进⾏发送操作,⽽是把⽤户态的数据copy 到系统底层去,然后再由系 统进⾏发送操作,send,write返回成功,只表示数据已经copy 到底层缓冲,⽽不表 示数据已经发出,更不能表示对⽅端⼝已经接收到数据.

#include<unistd.h>
//将数据写⼊已打开的⽂件内,写⼊count个字节到参数fd所指的⽂件内。
ssize_t write(int fd,const void*buf,size_t count);
//从已打开的⽂件中读取数据
ssize_t read(int fd,void*buf,size_t count);
返回值:读取到的实际数据数,如果返回0表示已经到达⽂件末尾或⽆可读取的数据,当read()函数
返回值为0时,
表示对端已经关闭了 socket,这时候也要关闭这个socket,否则会导致socket泄露。
当read()或者write()函数返回值⼤于0时,表示实际从缓冲区读取或者写⼊的字节数⽬
当read()或者write()返回-1时,⼀般要判断errno
⼀般是读写操作超时了,还未返回。这个超时是指socket的SO_RCVTIMEO与SO_SNDTIMEO两个属
性。
所以在使⽤阻塞socket时,不要将超时时间设置的过⼩。不然返回了-1,
你也不知道是socket连接是真的断开了,还是正常的⽹络抖动。⼀般情况下,阻塞的socket返回了
-1,
都需要关闭重新连接。
Close()和shutdown()——结束数据传输
当所有的数据操作结束以后,你可以调⽤close()函数来释放该socket,从⽽
停⽌在该socket上的任何数据操作:close(sockfd);

6. 关闭套接字以及连接的客户端

close(关闭的东西); 

客户端连接步骤:

1. 建立通讯套接字


 //1.创建通讯套接字 

  int clifd=socket(AF_INET,SOCK_STREAM,0);

2. 客户端建⽴socket连线

//2.客户端配置要连接服务器的参数

  struct sockaddr_in addr; 
  addr.sin_family=AF_INET; //IPV4
  addr.sin_port=htons(8000); //端口
  addr.sin_addr.s_addr=inet_addr("127.0.0.1"); //主机地址 --> 网络字节序
  int ret=connect(clifd,(struct sockaddr *)&addr,sizeof(addr)); //连接服务器
  if(ret==-1)
  {
     printf("connect failed\n");
     return -1;

  }

3. 读写数据

//3. 读取或者向服务端发送数据
  write(clifd,"hello",6);

4. 关闭套接字

// 4.close client
 close(clifd);

并发服务器

        TCP服务器⼀次只能接收⼀个客户端的连接的请求,只有在该客户端的所有请求都 满⾜后,服务器才可以继续响应后边的请求,如果⼀个客户端占⽤服务器不释放, 其他客户端都不能⼯作了,因此上述的TCP服务器⼜称为循环服务器,鉴于TCP循 环服务器的缺陷,很少TCP服务器采⽤。 为了解决循环TCP服务器的缺陷,⼈们⼜想出了并发服务器模型。并发服务器的思 想为:每⼀个客户端的请求并不由服务器直接处理,⽽是由服务器创建⼀个⼦进程 或⼦线程来解决。

1. 通过父子进程来接收和发送数据

//通过父子进程来接收和发送数据
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>

//服务器端----负责接收客户端发送的数据
#define SER_PORT  8001
#define SER_IP  "127.0.0.1"

void pro_sig(int sig)
{
  wait(NULL);
}


void talk(int fd)
{
  char buff[100]={0};
  char temp[100]={0};
  while(1)
  {
   int ret= read(fd,buff,100);   
   if(ret==0||strncmp(buff,"exit",4)==0)
   {
    break;
   }
   
   printf("recv data=%s\n",buff); 
   sprintf(temp,"%s:%s","from ser",buff);
   write(fd,temp,100); 
   memset(temp,0,100);
   memset(buff,0,100);
  
  }

  close(fd);


}


// build server program
int main()
{
//1.create socekt node
 int serfd=socket(AF_INET,SOCK_STREAM,0);
 if(serfd==-1)
 {

    printf("create socket failed\n");
    return -1;
 }
 //2.bind addr for server
 struct sockaddr_in addr;
 addr.sin_family=AF_INET;
 addr.sin_port=htons(SER_PORT);
 addr.sin_addr.s_addr=inet_addr(SER_IP);


int ret=bind(serfd,(struct sockaddr*)&addr,sizeof(addr));
if(ret==-1)
 {

  printf("bind failed\n");
  return -2;
 }

 //3.start listen
 ret=listen(serfd,10);
 if(ret==-1)
 {

   printf("listen failed\n");
   return -3;

 }

 //4. receive link from client computer

 signal(SIGCHLD,pro_sig); //父进程获取子进程结束的信号 并把子进程释放掉
 while(1)
 {

   printf("waitting connect ......\n");
  struct sockaddr_in cliaddr;
   int len=sizeof(cliaddr);
  int clifd= accept(serfd,(struct sockaddr*)&cliaddr,&len);
  if(clifd==-1)
  {

   printf("create client socket file failed\n");
   return -4;
   }
  printf("client ip=%s,port=%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

  pid_t pid=fork();
  if(pid==0)
  {
    talk(clifd);
    exit(0);
  }else if(pid==-1)
  { exit(0);}
 

 }


 close(serfd);

return 0;

}

2. 基于多进程构建的并发服务器 

//基于多进程构建的并发服务器
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>


//基于多进程构建的TCP/ip并发服务器
#define MAX_NUM  3

#define SER_PORT  8001
#define SER_IP  "127.0.0.1"

union semun
{
  int val;
};

int semid=-1;
void create_sem()
{
  key_t key=ftok("/bin/mkdir",2);
  semid=semget(key,1,IPC_CREAT|0600);
 if(semid==-1)
  {
    printf("create sem failed\n");
    exit(0);
   }
}


void init_sem(int val)
{
    union semun sem_val;
    sem_val.val=val;
  int ret=semctl(semid,0,SETVAL,sem_val);
  if(ret==-1)
   {
     printf("init sem failed\n");
	 exit(0);
   }

}


void sem_p()
{
   struct sembuf value;
  value.sem_num=0;
  value.sem_op=-1; 
value.sem_flg=SEM_UNDO;
int ret=semop(semid,&value,1);
if(ret==-1)
  {

   printf("operator sem failed\n"); 
   exit(0);
  }
}


void sem_v()
{
   struct sembuf value;
  value.sem_num=0;
  value.sem_op=+1; 
value.sem_flg=SEM_UNDO;
int ret=semop(semid,&value,1);
if(ret==-1)
  {
   printf("operator sem failed\n"); 
   exit(0);
  }
}

void destroy_sem()
{

   int ret=semctl(semid,0,IPC_RMID,NULL);
  if(ret==-1)
  {
    printf("destroy failed\n");
	exit(0);
  
  }
}



void pro_sig(int sig)
{

  wait(NULL);


}


void talk(int fd)
{

  char buff[100]={0};
  char temp[100]={0};
  while(1)
  {

   int ret= read(fd,buff,100);   
   if(ret==0||strncmp(buff,"exit",4)==0)
   {

    break;

   }
   printf("recv data=%s\n",buff); 
   sprintf(temp,"%s:%s","from ser",buff);
   write(fd,temp,100);
   memset(temp,0,100);
   memset(buff,0,100);
  
  }

  close(fd);
  printf("find child process exit\n");
  sem_v();

}


// build server program
int main()
{


  create_sem();
  init_sem(MAX_NUM);
//1.create socekt node
 int serfd=socket(AF_INET,SOCK_STREAM,0);
 if(serfd==-1)
 {

    printf("create socket failed\n");
    return -1;
 }
 //2.bind addr for server
 struct sockaddr_in addr;
 addr.sin_family=AF_INET;
 addr.sin_port=htons(SER_PORT);
 addr.sin_addr.s_addr=inet_addr(SER_IP);


int ret=bind(serfd,(struct sockaddr*)&addr,sizeof(addr));
if(ret==-1)
 {

  printf("bind failed\n");
  return -2;
 }

 //3.start listen
 ret=listen(serfd,10);
 if(ret==-1)
 {

   printf("listen failed\n");
   return -3;

 }

 //4. receive link from client computer

 signal(SIGCHLD,pro_sig);
 while(1)
 {

   printf("waitting connect ......\n");
  struct sockaddr_in cliaddr;
   int len=sizeof(cliaddr);

   sem_p();
  int clifd= accept(serfd,(struct sockaddr*)&cliaddr,&len);
  if(clifd==-1)
  {

   printf("create client socket file failed\n");
   return -4;
   }
  printf("client ip=%s,port=%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

  pid_t pid=fork();
  if(pid==0)
  {
    printf("create child process successed\n");
    talk(clifd);
    exit(0);
  }else if(pid==-1)
  { exit(0);}
 

 }


 close(serfd);
 destroy_sem();
return 0;

}

四、基于UDP/IP协议的Socket通信

4.1 基于UDP/IP通信的相关api简介

 1. 发送UDP报格式数据

#include <sys/types.h>
#include <sys/socket.h>
//把UDP数据报发给指定地址
int sendto (int sockfd, const void *buf, int len, unsigned int flags,
 const struct sockaddr *to, int tolen);
参数说明:
 sockfd 套接字⽂件描述符
 buf 存放发送的数据
 len 期望发送的数据⻓度
 flags 0
 to struct sockaddr_in类型,指明UDP数据发往哪⾥报
 tolen: 对⽅地址⻓度,⼀般为:sizeof(struct sockaddr_in)。 

2. 接收UDP报格式数据

#include <sys/types.h>
#include <sys/socket.h>
//接收UDP的数据
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
 struct sockaddr *from, int *fromlen);
参数意义和sentdo类似,其中romlen传递是接收到地址的⻓度
例如 int p=sizeof(struct adrr_in),最后⼀个参数就传为&p

基于udp/ip的通信基本案例

注意:如果数据流量突然增⼤,也可以通过如下函数设置发送或接收缓冲区的⼤ ⼩, 调整UDP缓冲区⼤⼩:使⽤函数setsockopt()函数修改接收缓冲区⼤⼩ int setsockopt(int sockfd,int level,int optname,const void *optval, socklen_t optlen); level:选项定义的层次:⽀持soL_SOCKET,IPPROTO_TCP,IPPROTO_IP,和 IPPROTO_IPV6optname:

需设置得选项so_RCVBUF(接收缓冲区),So_SNDBUF(发送缓冲区)

ljs@ljs-virtual-machine:~$ cat /proc/sys/net/ipv4/tcp_rmem //覆盖 net.cor
e.rmemmax
4096 131072 6291456 
读缓存最⼩值(4096)、默认值(87380)、最⼤值(6291456)(单位:字节),
ljs@ljs-virtual-machine:~$ cat /proc/sys/net/ipv4/tcp_wmem 
4096 16384 4194304
UDP接收缓冲区默认值:cat /proclsys/net/core/rmem_default

4.2 UDP⼴播

⼴播简介

        从上述讲的例⼦中,不管是TCP协议还是UDP协议,都是”单播”, 就是”点对点”的进⾏通信,如果要对⽹络⾥⾯的所有主机进⾏通信,实现”点对多”的通信,我们可 以使⽤UDP中的⼴播通信。

        理论上可以像播放电视节⽬⼀样在整个Internet 上发送⼴播数据,但是⼏乎没有路 由器转发⼴播数据,所以,⼴播程序只能应⽤在本地⼦⽹中。

        ⼴播的特点:

                1. ⼴播需要有发送⽅和接收⽅,必须有⼀些线程在机器上监听到来的数据。⼴播 的缺点是如果有多个进程都发送⼴播数据,⽹络就会阻塞,⽹络性能便会受到 影响。

                2. ⼴播发送不是循环给⽹络中的每⼀个IP发送数据,⽽是给⽹络中⼀个特定的IP 发送信息,这个IP就是⼴播地址,⼴播发送⽅:使⽤setsockopt打开 SO_BROADCAST, 设置⼴播地址 255.255.255.255,设置⼴播端⼝号。⼴播 接收⽅:将套接字绑定到指定的⼴播端⼝号, 监听数据到来

                3. ⼴播数据发送只能采⽤UDP协议,⼴播UDP与单播UDP的区别就是IP地址不 同,⼴播使⽤⼴播地址255.255.255.255,将消息发送到在同⼀⼴播⽹络上的每个主机。

setsockopt函数:
功能是⽤来为⽹络套接字设置选项值,具体如下:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层,整个⽹络协议中存在很多层,指定由哪⼀层解析;通
常是
SOL_SOCKET,也有IPPROTO_IP/IPPROTO_TCP。

optname:需要操作的选项名。常⻅的⽐如SO_BROADCAST 允许发送⼴播数
据 int
optval:对于setsockopt(),指向包含新选项值的缓冲(设置的选项值);对于
getsockopt(),指向返回选项值的缓冲。
optlen:对于getsockopt(),作为⼊⼝参数时,选项值的最⼤⻓度。作为出⼝参
数时,选项值的实际⻓度。对于setsockopt(),现选项的⻓度。
若⽆错误发⽣,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应⽤程序可通过
WSAGetLastError()获取相应错误代码。
如果希望端⼝断开后⽴即要使⽤,可以使⽤该函数的参数2设置为SO_REUSEADDR
⼀般来说,⼀个端⼝释放后会等待两分钟之后才能再被使⽤,SO_REUSEADDR
是让端⼝释放后⽴即就可以被再次使⽤。 ⽤于对TCP套接字处于TIME_WAIT状态
下的socket,才可以重复绑定使⽤

         INADDR_ANY代表指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地 址”、“任意地址”;表示本地上所有的IP地址。 因为有些机⼦不⽌⼀块⽹卡,多⽹卡的情况下,这个就表示所有⽹卡ip地址的意思。

INADDR_BROADCAST选项
INADDR_BROADCAST 代表255.255.255.255的⼴播地址,⼴播消息不会在当前路由器进⾏转发,
作⽤范围只能在当前局域⽹。
当在客户端⽹络编程中,如绑定的地址是INADDR_BROADCAST表示是⼴播通信。

例子:

ljs@ljs-virtual-machine:~/0808$ cat send.c recv.c
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

//所有人发送的信息所有人都能看得到别人发的内容
//利用UDP发送广播----广播的发送端
int main()
{
//1.create socket file
  int fd=socket(AF_INET,SOCK_DGRAM,0);

//2.enable broadcast function for socket
 int a=1; 
 int ret=setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&a,sizeof(int));
 if(ret==-1)
 {
  printf("set socket failed\n");
  return -1;
 }

 int count=0;
 char buff[20]={0};

 struct sockaddr_in addr;
 addr.sin_family=AF_INET;
 addr.sin_port=htons(8000);
 addr.sin_addr.s_addr=inet_addr("255.255.255.255");  //广播的固定地址
 while(1)
 {
   sprintf(buff,"%s--%d","广播",++count);
   sendto(fd,buff,20,0,(struct sockaddr*)&addr,sizeof(addr));
   sleep(1);
  if(count==1000) break;
 }

 close(fd);
 return 0;
}

//广播的接收端
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
//1.create socket file
  int fd=socket(AF_INET,SOCK_DGRAM,0);

//2. bind addr for reciver
 struct sockaddr_in addr;
 addr.sin_family=AF_INET;
 addr.sin_port=htons(8000);
 addr.sin_addr.s_addr=inet_addr("255.255.255.255");
 bind(fd,(struct sockaddr*)&addr,sizeof(addr));
char buff[20]={0};
 while(1)
 {
   recvfrom(fd,buff,20,0,NULL,NULL);  //因为广播的地址是固定的,所以不需要去获取它的地址
   printf("get data=%s\n",buff);
   memset(buff,0,20);
  
 }

 close(fd);

return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/911080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

字符串接龙 /单词接龙 (BFs C#

卡码网 110和 力扣127 和LCq 108题都是一个解法 这两道题乍一看在结果处可能不一样 力扣要求 字符串里边必须包含对应的最后一个字符 而110不需要最后一个字符 但是在实验逻辑上是一致的 只是110需要把如果在set中找不到最后一个字符就直接返回0的逻辑删去 就可以了 这就是…

Transformer和BERT的区别

Transformer和BERT的区别比较表&#xff1a; 两者的位置编码&#xff1a; 为什么要对位置进行编码&#xff1f; Attention提取特征的时候&#xff0c;可以获取全局每个词对之间的关系&#xff0c;但是并没有显式保留时序信息&#xff0c;或者说位置信息。就算打乱序列中token…

python操作MySQL以及SQL综合案例

1.基础使用 学习目标&#xff1a;掌握python执行SQL语句操作MySQL数据库软件 打开cmd下载安装 安装成功 connection就是一个类&#xff0c;conn类对象。 因为位置不知道&#xff0c;所以使用关键字传参。 表明我们可以正常连接到MySQL 演示、执行非查询性质的SQL语句 pytho…

【报告PDF附下载】2024人工智能大模型技术财务应用蓝皮书

《人工智能大模型技术财务应用蓝皮书》 是一本探讨AI大模型技术在财务管理领域应用的权威指南。书中不仅概述了人工智能大模型技术的发展历程、典型特征和未来趋势&#xff0c;还详细介绍了它的体系架构和在财务领域的应用情况。 书中通过家用电器制造、银行、汽车企业、基础设…

快速上手vue3+js+Node.js

安装Navicat Premium Navicat Premium 创建一个空的文件夹&#xff08;用于配置node&#xff09; 生成pakeage.json文件 npm init -y 操作mysql npm i mysql2.18.1 安装express搭建web服务器 npm i express4.17.1安装cors解决跨域问题 npm i cors2.8.5创建app.js con…

【Python爬虫实战】DrissionPage 与 ChromiumPage:高效网页自动化与数据抓取的双利器

&#x1f308;个人主页&#xff1a;易辰君-CSDN博客 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html ​ 目录 前言 一、DrissionPage简介 &#xff08;一&#xff09;特点 &#xff08;二&#xff09;安装 &#xff08;三…

【JAVA】java 企业微信信息推送

前言 JAVA中 将信息 推送到企业微信 // 企微消息推送messageprivate String getMessage(String name, String problemType, String pushResults, Long orderId,java.util.Date submitTime, java.util.Date payTime) {String message "对接方&#xff1a;<font color\…

前端md5加密

npm下载 npm install --save ts-md5页面引入 import { Md5 } from ts-md5使用 const md5PwdMd5.hashStr("123456")md5Pwd&#xff08;加密后的数据&#xff09; .toUpperCase()方法转大写

DDRSYS,不同频点的时序参数配置说明,DBI/DM功能说明

文章目录 不同频点的时序参数配置说明LPDDR4 时序参数DFI 参数对应配置DDR3/4DBI功能说明&#xff0c;MC控制DBI情况 不同频点的时序参数配置说明 LPDDR4 时序参数 LP4的时序参数从JEDEC颗粒文档可以检索到读写的时序参数如下&#xff1a; 此图主要关注不同频点对应的RL和WL…

如何自学机器学习?

自学机器学习可以按照以下步骤进行&#xff1a; 一、基础知识准备 数学基础&#xff1a; 高等数学&#xff1a;学习微积分&#xff08;包括导数、微分、积分等&#xff09;、极限、级数等基本概念。这些知识是后续学习算法和优化方法的基础。 线性代数&#xff1a;掌握矩阵…

工程巡查应该怎么做?如何利用巡查管理软件?

工程行业&#xff0c;无论是建设单位&#xff0c;监理单位&#xff0c;还是施工单位&#xff0c;工程巡查几乎是每日必做的工作。然而&#xff0c;巡查过程中&#xff0c;传统的做法通常依赖手动记录、拍照上传、在微信群中进行汇报。这种方式需要建大量的微信群&#xff0c;不…

Scala入门基础(16)scala的包

Scala的包定义包定义包对象Scala的包的导入导入重命名 一.Scala的包 package&#xff08;包&#xff1a;一个容器。可以把类&#xff0c;对象&#xff0c;包&#xff0c;装入。 好处&#xff1a; 区分同名的类&#xff1b;类很多时&#xff0c;更好地管理类&#xff1b;控制…

协程6 --- HOOK

文章目录 HOOK 概述链接运行时动态链接 linux上的常见HOOK方式修改函数指针用户态动态库拦截getpidmalloc 第一版malloc 第二版malloc/free通过指针获取到空间大小malloc 第三版strncmp 内核态系统调用拦截堆栈式文件系统 协程的HOOK HOOK 概述 原理&#xff1a;修改符号指向 …

MySQL中,GROUP BY 分组函数

文章目录 示例查询&#xff1a;按性别分组统计每组信息示例查询&#xff1a;按性别分组显示详细信息示例查询&#xff1a;按性别分组并计算平均年龄,如果你还想统计每个性别的平均年龄&#xff0c;可以结合AVG()函数&#xff1a;说明 示例查询&#xff1a;按性别分组统计每组信…

免费数据集网站

1、DataSearch https://datasetsearch.research.google.comhttp://DataSearch 2、FindData findata-科学数据搜索引擎https://www.findata.cn/ 3、Kaggle Kaggle: Your Machine Learning and Data Science CommunityKaggle is the world’s largest data science community …

十二:java web(4)-- Spring核心基础

目录 创建项目 Spring 核心基础 Spring 容器 Spring 容器的作用 Spring 容器的工作流程 Bean Bean 的生命周期 IOC&#xff08;控制反转&#xff09;与依赖注入&#xff08;DI&#xff09; 控制反转的概念 依赖注入的几种方式&#xff08;构造器注入、Setter 注入、接…

MybatisPlus入门(八)MybatisPlus-DQL编程控制

一、字段映射与表名映射 数据库表和实体类名称一样自动关联&#xff0c;数据库表和实体类有部分情况不一样。 问题一&#xff1a;表名与编码开发设计不同步&#xff0c;表名和实体类名称不一致。 解决办法&#xff1a; 在模型类上方&#xff0c;使用TableName注解&#xf…

亚信安全新一代WAF:抵御勒索攻击的坚固防线

近年来&#xff0c;勒索攻击已成为黑客的主要攻击手段。新型勒索攻击事件层出不穷&#xff0c;勒索攻击形势愈发严峻&#xff0c;已经对全球制造、金融、能源、医疗、政府组织等关键领域造成严重危害。如今&#xff0c;勒索攻击手段日趋成熟、攻击目标愈发明确&#xff0c;模式…

代谢组数据分析(二十一):通过MetaboAnalystR标准化构建sPLSDA预测模型

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍MetaboAnalystR标准化sPLSDA分析安装需要的R包加载R包导入数据MetaboAnalystR标准化数据初始化数据清洗数据补足数据过滤数据标准化导出结果sPLSDA分析导入数据数据预处理PCA分析PL…

《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明

参考 《element plus 使用 icon 图标(两种方式)》使用 icon 升级 Vue2 升级 Vue3 项目时&#xff0c;遇到命名时的实心与空心点差异&#xff01; ElementUI&#xff1a; 实心是 el-icon-more空心是 el-icon-more-outline ElementPlus&#xff1a; 实心是 el-icon-more-fill…