Linux 高级Socket编程

时间:2021-08-07 09:15:44

设置套接字函数:

#include<sys/socket.h>

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

//sockfd要设置的目的套接字
//level套接字的控制层次
//optname optval optlen是三个相关的参数,通过不同的搭配可以设置不同的功能

应用:

1.数据收发时限设置

struct timeva timeout;
timeout.tv_sec
=5;
timeout.tv_usec
=0;

//接受时限
setsockopt(serversocket, SQL_SOCKET,SO_RCVTIMEO, (char*)&timeout,sizeof(timeout));

//发送时限
setsockopt(serversocket, SQL_SOCKET,SO_SNDTIMEO, (char*)&timeout,sizeof(timeout));

2.修改收发缓冲区

//接收缓冲区
int opt=1024*1024;
setsockopt(serversocket, SQL_SOCKET, SO_RCVBUF, (
const char*)&opt,sizeof(opt));


//发送缓冲区
setsockopt(serversocket, SQL_SOCKET, SO_SNDBUF, (const char*)&opt,sizeof(opt));

3.广播设置

int bBroadcast=1;
setsockopt(seversocket, SQL_SOCKET, SO_BROADCAST,(cosnt
char*)&bBroadcast,sizeof(bBroadcast));

4.直接数据复制

  为了提升系统性能,在发送或接受数据时,可以主动设置数据不经历由缓冲区到套接字缓存区的拷贝。

int opt=0;

setsockopt(serversocket, SQL_SOCKET,SO_SNDBUF,(
char*)&opt,sizeof(opt));

setsockopt(serversocket, SQL_SOCKET,SO_RCVBUF,(
char*)&opt,sizeof(opt));

 

Select技术:

#include <stdio.h>
#include
<sys/socket.h>
#include
<unistd.h>
#include
<stdlib.h>
#include
<sys/types.h>
#include
<errno.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<string.h>

#define SERVER_PORT 5555
#define QUEUE_LENGTH 5
#define BUF_SIZE 200

int main(int argc, char **argv)
{
int server_socket,new_socket;
struct sockaddr_in server_addr,client_addr;
socklen_t sin_size;
int client_socket[QUEUE_LENGTH];
int conn_num;
int yes=1;
char buf[BUF_SIZE];
int ret;
int i;
//创建套接字
if((server_socket=socket(AF_INET,SOCK_STREAM,0))<0){
perror(
"Socket");
return 0;
}
//设置为可重复使用
if(setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))==-1){
perror(
"setsockopt");
return 0;
}
//设置服务器地址信息设置
server_addr.sin_family=AF_INET; //TCP
server_addr.sin_port=htons(SERVER_PORT);
server_addr.sin_addr.s_addr
=INADDR_ANY; //本地IP地址

memset(server_addr.sin_zero,
'\0',sizeof(server_addr.sin_zero));

//绑定套接字与地址信息
if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
perror(
"setsockopt");
return 0;
}

//侦听
if(listen(server_socket,5)==-1){
perror(
"setsockopt");
return 0;
}

printf(
"listen port : %d\n",SERVER_PORT);

fd_set clientfdset;
int maxsock;
struct timeval tv;

conn_num
=0;
sin_size
=sizeof(client_addr);
maxsock
=server_socket;

while(1){
//初始化,清空并添加服务器套接字到集合
FD_ZERO(&clientfdset);
FD_SET(server_socket,
&clientfdset);

//设置超时时间
tv.tv_sec=15;
tv.tv_usec
=0;

//添加连接的客户端到集合
for(i=0;i<QUEUE_LENGTH;i++){
if(client_socket[i]!=0)
FD_SET(client_socket[i],
&clientfdset);
}
//select模式
ret=select(maxsock+1,&clientfdset,NULL,NULL,&tv);
if(ret<0){
perror(
"select");
break;
}
else if(ret==0){
printf(
"waiting timeout\n");
continue;
}

//检查集合内是否已经存在
for(i=0;i<conn_num;i++){
if(FD_ISSET(client_socket[i],&clientfdset)){
ret
=recv(client_socket[i],buf,sizeof(buf),0);

if(ret<=0){
printf(
"client[%d] close\n",i);
close(client_socket[i]);
FD_CLR(client_socket[i],
&clientfdset);
client_socket[i]
=0;
}
else{
printf(
"client[%d] msg: %s\n",i,buf);
send(client_socket[i],buf,
sizeof(buf),0);
}
}
}

if(FD_ISSET(server_socket,&clientfdset)){
new_socket
=accept(server_socket,(struct sockaddr*)&client_addr,&sin_size);
if(new_socket<=0){
perror(
"accept");
continue;
}
if(conn_num<QUEUE_LENGTH){
client_socket[conn_num
++]=new_socket;
printf(
"new client[%d] %s: %d\n",conn_num,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
if(new_socket>maxsock)
maxsock
=new_socket;
}
else{
send(new_socket,
"sorry overload!",sizeof("sorry overload!"),0);
close(new_socket);
break;
}
}
}

for(i=0;i<QUEUE_LENGTH;i++){
if(client_socket[i]!=0)
close(client_socket[i]);
}

}

 原始套接字技术:

  原始套接字是一种套接字底层技术,它工作在网络层。利用原始套接字可以完成如下功能。

  •   设置网卡为混杂模式,嗅探当前网路流经本网卡的所有数据包。
  •   构造各种数据包(IP,ICMP,TCP,UDP等),并进行发送。
  •   进行新协议的验证。 

  原始套接字可用于木马中的通信模块,伪造IP地址,拒绝服务攻击,数据包嗅探。

原始套接字的创建:

int rawsock=socket(AF_INET, SOCK_RAW, htons(ETH_P_IP));
//可以获取IP层的所有数据报文

htons参数的可选值及其意义
协议码 协议名
IPPROTO_ICMP ICMP协议
ETH_P_IP IP协议
IPPROTO_TCP TCP协议
IPPROTO_UDP UDP协议
IPPROTO_IPV6 IPv6协议
IPPROTO_EGP EGP协议
 

数据发送:

  在原始套接字中,执行数据发送前要条用setsocketopt函数进行套接字的首部设定:

int opt;
setsockopt(sockfd,IPPROTO_IP, IP_HDRINCL,
&opt, sizeof(opt));

例子:

//利用原始套接字实现一个简单的采集网络数据包,并进行反向解析IP,MAC地址
#include <stdio.h>#include <sys/socket.h>
#include
<unistd.h>
#include
<sys/types.h>
#include
<linux/if_ether.h>
#include
<linux/in.h>

#define BUFFER_MAX 2048

int main(int argc, char **argv)
{
int rawsock;
char buffer[BUFFER_MAX];
char *ethhead;
char *iphead;
char *phead;

//创建原始套接字
if((rawsock=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP)))<0){
printf(
"error:create raw socket!\n");
exit(
0);
}

long framecount =0;

while(1){
int readnum = recvfrom(rawsock,buffer,2048,0,NULL,NULL);

if(readnum<42){
printf(
"error:header is incomplete!\n");
exit(
0);
}

ethhead
=(char*)buffer;
phead
=ethhead;
int ethernetmask=0XFF;
framecount
++;

printf(
"---------------AnalysisiPacket[%d]---------------\n",framecount);
printf(
"MAC:");
int i=6;
for(;i<=11;i++)
printf(
"%.2X:",phead[i]&ethernetmask);
printf(
"------->");
for(i=0;i<=5;i++)
printf(
"%.2X:",phead[i]&ethernetmask);
printf(
"\n");

iphead
=ethhead+14;
phead
=iphead+12;

printf(
"IP:");
for(i=0;i<=3;i++){
printf(
"%d",phead[i]&ethernetmask);
if(i!=3)
printf(
".");
}
printf(
"------->");
for(i=4;i<=7;i++){
printf(
"%d",phead[i]&ethernetmask);
if(i!=7)
printf(
".");
}
printf(
"\n");

int prototype=(iphead+9)[0];
phead
=iphead+20;

printf(
"Protocol:");
switch(prototype){
case IPPROTO_ICMP:
printf(
"ICMP\n");
break;
case IPPROTO_IGMP:
printf(
"IGMP\n");
break;
case IPPROTO_IPIP:
printf(
"IP");
break;
case IPPROTO_TCP:
printf(
"TCP|source port: %u |",(phead[0]<<8)&0XFF00|phead[1]&0XFF);
printf(
"destport: %u\n",(phead[2]<<8)&0XFF00|phead[3]&0XFF);
break;
case IPPROTO_UDP:
printf(
"UDP|source port: %u |",(phead[0]<<8)&0XFF00|phead[1]&0XFF);
printf(
"destport: %u\n",(phead[2]<<8)&0XFF00|phead[3]&0XFF);
break;
case IPPROTO_RAW:
printf(
"RAW\n");
break;
default:
printf(
"Unkown\n");
}
printf(
"-----------------end--------------------");
}

return 0;
}

 

广播技术:

  ARP(Address Resolution Protocol)和NTP(Network Time Protocol)都属于广播通信。

    ARP是局域网中的地址解析协议,利用这个协议,可以找出IP地址到MAC地址的映射关系。当主机A准备与主机B通信时,如果只知道主机B的IP地址,则主机A向整个全网发送一个ARP请求,询问IP地址为XXXX的主机,如果主机B收到就会产生回应。

  NTP是网络时间协议。在支持广播的局域网中设置NTP协议,可以使NTP服务器每隔一个固定的时间间隔,就向全网发送时间信息,客户端在收到时间信息后进行更新处理。

 

原理解析:

  要进行广播通信,首先要理解广播地址。在IP地址中,如果最后一个数字是255,则一定是一个广播地址。

  • 网络广播地址:网络广播地址在没有进行子网划分的网络内广播,由于当强的网络均涉及子网划分,故此种地址很少存在
  • 受限广播地址:以255.255.255.255组成的广播地址,在当前路由器均不转发此类广播
  • 子网广播地址:子网广播地址是一种常用的广播方式,它是指在一个具体的子网内进行广播,比如192.168是网络ID,那么192.168.1.255就是子网192.168.1的广播
  • 全部子网广播地址:是指所有子网络的广播,以上一个为例,全部子网广播地址是192.168.255.255 

 

广播要采用UDP的方式,具体流程如下:

  1. 创建UDP套接字
  2. 设置套接字属性为SO_BROADCAST,设置为广播地址
  3. 设置广播地址为INADDR_BROADCAST,同时也要指定发送端口
  4. 进行数据收发操作

例子:

//bserver.c
#include <sys/types.h>
#include
<stdio.h>
#include
<sys/socket.h>
#include
<stdlib.h>
#include
<string.h>
#include
<netdb.h>
#include
<errno.h>

#define BUFFSIZE 200
#define PORT 5050

int main(int argc, char **argv)
{
int serversocket;
struct sockaddr_in serveraddress,clientaddress;

int so_broadcast=1;


if((serversocket=socket(AF_INET,SOCK_DGRAM,0))<0){
perror(
"socket");
return 0;
}

if(setsockopt(serversocket,SOL_SOCKET,SO_BROADCAST,&so_broadcast,sizeof(so_broadcast))<0){
perror(
"setsockopt");
return 0;
}

serveraddress.sin_family
=AF_INET;
serveraddress.sin_port
=htons(INADDR_ANY);
serveraddress.sin_addr.s_addr
=htonl(INADDR_BROADCAST);

if(bind(serversocket,(struct sockaddr*)&serveraddress,sizeof(struct sockaddr))<0){
perror(
"bind");
return 0;
}

clientaddress.sin_family
=AF_INET;
clientaddress.sin_port
=htons(PORT);
clientaddress.sin_addr.s_addr
=htonl(INADDR_BROADCAST);

while(1){
char buf[BUFFSIZE];
printf(
"please input your word:");
scanf(
"%s",buf);
if(sendto(serversocket,buf,strlen(buf),0,(struct sockaddr*)&clientaddress,sizeof(clientaddress))<0){
perror(
"sendto");
return 0;
}
else
printf(
"send msg: %s\n",buf);
}

return 0;
}
//bclient.c
#include <sys/types.h>
#include
<stdio.h>
#include
<sys/socket.h>
#include
<stdlib.h>
#include
<string.h>
#include
<netdb.h>
#include
<errno.h>

int main(int argc, char **argv)
{
int clientsocket;
struct sockaddr_in serveraddress,clientaddress;

clientsocket
=socket(AF_INET,SOCK_DGRAM,0);

serveraddress.sin_family
=AF_INET;
serveraddress.sin_port
=htons(5050);
serveraddress.sin_addr.s_addr
=htonl(INADDR_ANY);

int opt=1;
if(setsockopt(clientsocket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0){
perror(
"setsockopt");
return 0;
}

if(bind(clientsocket,(struct sockaddr*)&serveraddress,sizeof(struct sockaddr))!=0){
perror(
"bind");
return 0;
}

char buf[200];

while(1){
memset(buf,
0,200);
int size=0;
size
=recvfrom(clientsocket,buf,200,0,(struct sockaddr*)&serveraddress,sizeof(serveraddress));
buf[size]
='\0';
printf(
"IP:%s msg:%s\n",inet_ntoa(clientaddress.sin_addr),buf);

if(strcmp(buf,"quit")==0){
printf(
"system quit!\n");
close(clientsocket);
return 0;
}
}

return 0;
}

 

组播技术:

  组播可以实现小范围内的互联,在发送者和每一个接受者之间时间点对多点的网络连接,是广播通信的一种变种。

  根据IP地址的规定,D类地址为组播地址,其网络号为固定的1110,第4到31位定义了某一特殊的组播地址,范围为244.0.0.0~239.255.255.255。其中244.0.0.0~244.0.0.255的地址,它们大多是为了特殊的目的保留的,不建议使用。

套接字的基本属性:组播参数对应5个参数,通过setsockopt设置

//加入组播
int setsockopt(client_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&multiaddress,sizeof(multiaddress))

//退出组播
int setsockopt(client_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&multiaddress,sizeof(multiaddress))

//这里有一个重要的参数multiaddress,结构:
struct ip_mreq{
struct in_addr imr_multiaddr; //组播地址
struct in_addr imr_interface; //IPv4地址
}

主要流程:

  1. 服务器端设置一个多播地址,创建一个多播组。
  2. 客户端指定多播地址,加入多播。
  3. 程序结束后,退出多播。

例子:

//memberServer.c

#include
<stdio.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<netdb.h>
#include
<unistd.h>
#include
<stdlib.h>
#include
<string.h>

int main(int argc, char **argv)
{
int server_socket;
struct sockaddr_in address;

//创建UDP
server_socket=socket(AF_INET,SOCK_DGRAM,0);
if(server_socket<0){
perror(
"socket");
return 0;
}

//初始化多播地址
memset(&address,0,sizeof(address));
address.sin_family
=AF_INET;
address.sin_port
=htons(5555);
address.sin_addr.s_addr
=inet_addr("224.0.1.100");

//发送信息
while(1){
char buf[200];
printf(
"input your word:");
scanf(
"%s",buf);
if(sendto(server_socket,buf,sizeof(buf),0,(struct sockaddr*)&address,sizeof(address))<0){
perror(
"sendto");
return 0;
}
}

return 0;
}
//memberClient.c

#include
<stdio.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<netdb.h>
#include
<unistd.h>
#include
<stdlib.h>
#include
<string.h>

int main(int argc, char **argv)
{
struct ip_mreq mreq;
int serveraddress_len;
int client_socket;
struct sockaddr_in serveraddress;

//初始化地址
memset(&serveraddress,0,sizeof(serveraddress));
serveraddress.sin_family
=AF_INET;
serveraddress.sin_port
=htons(5555);
serveraddress.sin_addr.s_addr
=htonl(INADDR_ANY);

if((client_socket=socket(AF_INET,SOCK_DGRAM,0))<0){
perror(
"client");
return 0;
}

//绑定SOCKET
if(bind(client_socket,(struct sockaddr*)&serveraddress,sizeof(serveraddress))<0){
printf(
"bind");
return 0;
}

int opt=1;
if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0){
printf(
"setsockopt1");
return 0;
}

//加入多播
mreq.imr_multiaddr.s_addr=inet_addr("244.0.1.100");
mreq.imr_interface.s_addr
=htonl(INADDR_ANY);

if(setsockopt(client_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0){
perror(
"setsockopt2");
return 0;
}

while(1){
char buf[200];
serveraddress_len
=sizeof(serveraddress);
if(recvfrom(client_socket,buf,200,0,(struct sockaddr*)&serveraddress,(socklen_t *)serveraddress_len)<0){
perror(
"recvfrom");
}
printf(
"msg from server: %s\n",buf);

if(strcmp(buf,"quit")==0){
if(setsockopt(client_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq))<0){
perror(
"setsokopt3");
}
close(client_socket);
return 0;
}
}

return 0;
}