C语言socket的简单使用

套接字介绍

多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。
为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
常用的TCP/IP协议的3种套接字类型如下所示。

1.流套接字(SOCK_STREAM):

流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。
流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

2.数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。

该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。
由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

3.原始套接字(SOCK_RAW):原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:

原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,
数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
不同操作系统中的Socket

Windows Socket (Winsock)
Linux Socket (BSD Socket)

`

`

网络编程中各协议首部结构体定义

1、IP数据报首部的固定部分中的各字段

(1)版本 占4位,指IP协议的版本。通信双方使用的IP协议版本必须一致。目前广泛使用的IP协议版本号为4(即IPv4)。

(2)首部长度 占4位,可表示的最大十进制数值是15。请注意,这个字段所表示数的单位是32位字长(1个32位字长是4字节),

因此,当IP的首部长度为1111时(即十进制的15),首部长度就达到60字节。当IP分组的首部长度不是4字节的整数倍时,
必须利用最后的填充字段加以填充。因此数据部分永远在4字节的整数倍开始,这样在实现IP协议时较为方便。首部长度限制为60
字节的缺点是有时可能不够用。但这样做是希望用户尽量减少开销。最常用的首部长度就是20字节(即首部长度为0101),这时不使用任何选项。
(#我们一般看到的版本和首部长度两个字段是十六进制45,就是版本号version=4,headlength=5,也就是首部长度是60个字节)

(3)区分服务 占8位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。

1998年IETF把这个字段改名为区分服务DS(Differentiated Services)。只有在使用区分服务时,这个字段才起作用。

(4)总长度 总长度指首部和数据之和的长度,单位为字节。总长度字段为16位,因此数据报的最大长度为216-1=65535字节。

#可以看这个以太网frame总长为336字节,而IP数据包Total length=322,336-322=14正好是Ethernet包头的长度,
所以就可以看出这IP数据包总长度一值就是除去Ethernet头的剩余长度,也就是IP包头加数据的长度。
在IP层下面的每一种数据链路层都有自己的帧格式,其中包括帧格式中的数据字段的最大长度,这称为最大传送单元MTU(Maximum Transfer Unit)。
当一个数据报封装成链路层的帧时,此数据报的总长度(即首部加上数据部分)一定不能超过下面的数据链路层的MTU值。

(5)标识(identification) 占16位。IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值赋给标识字段。

但这个“标识”并不是序号,因为IP是无连接服务,数据报不存在按序接收的问题。当数据报由于长度超过网络的MTU而必须分片时,
这个标识字段的值就被复制到所有的数据报的标识字段中。相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报。

(6)标志(flag) 占3位,但目前只有2位有意义。

  • 标志字段中的最低位记为MF(More Fragment)。MF=1即表示后面“还有分片”的数据报。MF=0表示这已是若干数据报片中的最后一个。
  • 标志字段中间的一位记为DF(Don’t Fragment),意思是“不能分片”。只有当DF=0时才允许分片。

    (7)占13位。片偏移指出:较长的分组在分片后,某片在原分组中的相对位置。

    也就是说,相对用户数据字段的起点,该片从何处开始。
    片偏移以8个字节为偏移单位。这就是说,每个分片的长度一定是8字节(64位)的整数倍。

    (8)生存时间 占8位,生存时间字段常用的的英文缩写是TTL(Time To Live),表明是数据报在网络中的寿命。

    由发出数据报的源点设置这个字段。其目的是防止无法交付的数据报无限制地在因特网中兜圈子,因而白白消耗网络资源。
    最初的设计是以秒作为TTL的单位。每经过一个路由器时,就把TTL减去数据报在路由器消耗掉的一段时间。
    若数据报在路由器消耗的时间小于1秒,就把 TTL值减1。当TTL值为0时,就丢弃这个数据报。
    #TTL通常是32或者64,scapy中默认是64

    (9)协议 占8位,协议字段指出此数据报携带的数据是使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程。

    (在scapy中,下层的这个protocol一般可以从上曾继承而来,自动填充,我们一般可以省略不填此项)

    (10)首部检验和 占16位。这个字段只检验数据报的首部,但不包括数据部分。这是因为数据报每经过一个路由器,

    路由器都要重新计算一下首部检验和(一些字段,如生存时间、标志、片偏移等都可能发生变化)。不检验数据部分可减少计算的工作量。

    (11)源地址 占32位。

    (12)目的地址 占32位。

    `

`

IP首部

1
2
3
4
5
6
7
8
9
10
11
12
13
  typedef struct IpHeader
{
unsigned char Version_HLen;//版本号 首部长度
unsigned char TOS;//服务类型
unsigned short Length;//总长度
unsigned short Ident; //标识
unsigned short Flags_Offset; //标志 片偏移
unsigned char TTL; //生存时间
unsigned char Protocol; //协议
unsigned short Checksum; //首部校验和
unsigned int SourceAddr; //源地址
unsigned int DestinationAddr; //目的地址
} Ip_Header;

`

`

TCP首部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 //TCP的标志
#define URG 0x20
#define ACK 0x10
#define PSH 0x08
#define RST 0x04
#define SYN 0x02
#define FIN 0x01
//定义TCP首部
typedef struct TcpHeader
{
USHORT SrcPort;//16位源端口
USHORT DstPort; //16位目的端口
unsigned int SequenceNum; //32位序号
unsigned int Acknowledgment; //32为确认序号
unsigned char HdrLen; //首部长度
unsigned char Flags; //6位标志位
USHORT AdvertisedWindow; //16位窗口大小
USHORT Checksum; //16位校验和
USHORT UrgPtr; //16位紧急指针
} Tcp_Header;

`

`

TCP伪首部

1
2
3
4
5
6
7
8
 typedef struct PsdTcpHeader
{
unsigned long SourceAddr;
unsigned long DestinationAddr;
char Zero;
char Protcol;
unsigned short TcpLen;
} PSD_Tcp_Header;

原始套接字编程

Winsock头文件

开始进行Winsock编程,通过包含Winsock2.h来使用Winsock的API。(Winsock2.h头文件已经包含了大部分Winsock函数、
数据结构和定义,Ws2tcpip.h头文件包含了在Winsock2协议兼容文档中为TCP/IP用于检索IP地址的新函数和数据结构。

接收端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 #include<stdio.h>
#include<stdlib.h>
#include<Winsock2.h>
#pragma comment(lib,"wsock32.lib")

int main(){
struct WSAData wsaData;
SOCKET RecSocket;//接收用的socket
SOCKET SenSocket;//发送用的socket
char Name[255];
char buffer[1024]={0};//输入缓冲区
int Result;
struct hostent *pHostent;
SOCKADDR_IN sock;
SOCKADDR_IN senAddr;
int flag;
int nTimeOver=1000;//超时时间
int nSize=sizeof(SOCKADDR);

//初始化socket
Result=WSAStartup(MAKEWORD(2,2),&wsaData);
if(Result==SOCKET_ERROR){
printf("WSAStartup failed with error %d\n",Result);
return 0;
}
//打印wsaData信息
printf("wsaData.wVersion=%d\n-----\n",wsaData.wVersion);
printf("wsaData.wHighVersion=%d\n-----\n",wsaData.wHighVersion);
printf("wsaData.szDescription=%s\n-----\n",wsaData.szDescription);
printf("wsaData.szSystemStatus=%s\n-----\n",wsaData.szSystemStatus);
printf("wsaData.iMaxSockets=%d\n-----\n",wsaData.iMaxSockets);
printf("wsaData.iMaxUdpDg=%d\n-----\n",wsaData.iMaxUdpDg);
printf("wsaData.lpVendorInfo=%p\n-----\n",wsaData.lpVendorInfo);

//创建原始套接字
RecSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(RecSocket==INVALID_SOCKET){
printf("socket failed with error %d\n",WSAGetLastError());
closesocket(RecSocket);
return 0;
}


//初始化sock变量
Result=gethostname(Name,255);
if(Result==SOCKET_ERROR){
printf("gethostname failed with error %d\n", WSAGetLastError());
return 0;
}
pHostent=(struct hostent*)malloc(sizeof(struct hostent));
pHostent=gethostbyname(Name);
sock.sin_family=AF_INET;
sock.sin_port=htons(5555);
memcpy(&sock.sin_addr.S_un.S_addr,pHostent->h_addr_list[0],pHostent->h_length);



//绑定套接字
Result=bind(RecSocket,(PSOCKADDR)&sock,sizeof(sock));
if(Result==SOCKET_ERROR){
printf("bind failed with error %d\n", WSAGetLastError());
closesocket(RecSocket);
return 0;
}
/*
//设置套接字
//设置手工填充IP数据包首部
flag=1;
if(setsockopt(SendSocket,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(flag))==SOCKET_ERROR){
printf("setsockopt failed with error %d\n\n",WSAGetLastError());
return 0;
}
//设置超时时间
if(setsockopt(SendSocket,SOL_SOCKET,SO_SNDTIMEO,(char *)&nTimeOver,sizeof(nTimeOver))==SOCKET_ERROR){
printf("setsockopt failed with error %d\n\n", WSAGetLastError());
return 0;
}
//设置SOCK_RAW为SIO_RCVALL,接受数据包
Result=WSAIoctl(RecSocket,SIO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferInLen,sizeof(dwBufferInLen),&dwBytesReturned,NULL,NULL);
if(Result==SOCKET_ERROR){
printf("WSAIoctl failed with error %d\n", WSAGetLastError());
closesocket(RecSocket);
return 0;
}
*/
//进入监听状态
Result=listen(RecSocket,10);
if(Result==SOCKET_ERROR){
printf("listen failed with error %d\n", WSAGetLastError());
closesocket(RecSocket);
return 0;
}

//接受客户端请求
printf("wait for connect....\n");
SenSocket=accept(RecSocket,(PSOCKADDR)&senAddr,&nSize);
if(SenSocket==INVALID_SOCKET){
printf("listen failed with error %d\n", WSAGetLastError());
closesocket(RecSocket);
return 0;
}
printf("connect success!!!\n");


/**
这里写接收文件的逻辑,目前只是接收字符串,后面再改
*/

while(recv(SenSocket,buffer,1024,0)){
printf("msg is : %s\n",buffer);
if(strcmp(buffer,"quit")==0){
break;
}
memset(buffer,0,1024);
}

//关闭套接字
closesocket(SenSocket);
closesocket(RecSocket);

//释放winstock
if(WSACleanup()==SOCKET_ERROR){
printf("WSACleanup failed with error %d\n", WSAGetLastError());
return 0;
}
printf("success!!\n");
return 1;
}

发送端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
 #include<stdio.h>
#include<stdlib.h>
#include<Winsock2.h>
#pragma comment(lib,"wsock32.lib")

int main(){
struct WSAData wsaData;
int Result;
SOCKET SenSocket;
SOCKADDR_IN senAddr;
char Name[255];
char inputBuffer[1024]={0};
struct hostent *pHostent;


//初始化
Result=WSAStartup(MAKEWORD(2,2),&wsaData);
if(Result==SOCKET_ERROR){
printf("WSAStartup failed with error %d\n",Result);
return 0;
}
//打印wsaData信息
printf("wsaData.wVersion=%d\n-----\n",wsaData.wVersion);
printf("wsaData.wHighVersion=%d\n-----\n",wsaData.wHighVersion);
printf("wsaData.szDescription=%s\n-----\n",wsaData.szDescription);
printf("wsaData.szSystemStatus=%s\n-----\n",wsaData.szSystemStatus);
printf("wsaData.iMaxSockets=%d\n-----\n",wsaData.iMaxSockets);
printf("wsaData.iMaxUdpDg=%d\n-----\n",wsaData.iMaxUdpDg);
printf("wsaData.lpVendorInfo=%p\n-----\n",wsaData.lpVendorInfo);


//创建原始套接字
SenSocket=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
if(SenSocket==INVALID_SOCKET){
printf("socket failed with error %d\n",WSAGetLastError());
closesocket(SenSocket);
return 0;
}

//初始化sock变量
Result=gethostname(Name,255);
if(Result==SOCKET_ERROR){
printf("gethostname failed with error %d\n", WSAGetLastError());
return 0;
}
pHostent=(struct hostent*)malloc(sizeof(struct hostent));
pHostent=gethostbyname(Name);
senAddr.sin_family=AF_INET;
senAddr.sin_port=htons(5555);
memcpy(&senAddr.sin_addr.S_un.S_addr,pHostent->h_addr_list[0],pHostent->h_length);

//连接服务器
Result=connect(SenSocket,(PSOCKADDR)&senAddr,sizeof(senAddr));
if(Result==SOCKET_ERROR){
printf("connect failed with error %d\n",WSAGetLastError());
closesocket(SenSocket);
return 0;
}

/**
*这里发送数据
*/
while(1){
printf("please input some message or input quit to quit:\n");
//scanf("%s",inputBuffer);
gets(inputBuffer);
if(SOCKET_ERROR==send(SenSocket,inputBuffer,strlen(inputBuffer),0)){
printf("send failed with error %d\n",WSAGetLastError());
return 0;
}
printf("send success!!!\n");
if(strcmp(inputBuffer,"quit")==0){
break;
}
}

//关闭socket
closesocket(SenSocket);

//释放winstock
if(WSACleanup()==SOCKET_ERROR){
printf("WSACleanup failed with error %d\n", WSAGetLastError());
return 0;
}
printf("success!!\n");
return 1;

}