【网络编程1】网络编程基础-TCP、UDP编程

时间:2022-05-13 13:07:12

网络基础知识

网络模型知识

OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准化框架,简称OSI。

TCP/IP模型:(tcp/ip reference model)TCP/IP参考模型,是一组用于实现网络互连的通信协议,其名称来源于该协议簇中两个重要的协议(IP协议和TCP协议)。

internet网络体系结构以TCP/IP为核心。基于TCP/IP的参考模型将协议分成四个层次,分别是:应用层、传输层、网络层、数据链路层(接口层)

TCP/IP模型

发送方 行为 接收方
应用层 进程产生一些数据
传输层 主机或代理服务器规划怎么传输数据,保证传输正确
网络层 网络中各个节点路由转发数据包
数据链路层 物理媒介,将数字信号转换成模拟信号,传递信息。

数据从应用层出发,各层为了实现自己的任务,需要传递一些数据,这些数据就以一种“外壳”的形式把上一层发来的数据包裹起来。

当数据到达目标主机后,各层再从经过的数据包上剥下“外壳”,这个外壳是数据出发时流经同一层时给添加上的,这样不同的进程,在相同的层之间便完成了数据的交换,从而可以完成本层的任务。

网络模型中一组协议协同工作时,我们称之为协议栈。

网络模型与协议

(1)应用层:相当于OSI模型的上面3层,是负责处理各类应用程序的发送数据包的所有细节问题;

(2)传输层:包括传输控制协议(TCP)和用户数据报协议(UDP) ;

(3)网络层:包括因特网协议(IP),地址解析协议(ARP),反向地址解析协议(RARP)和因特网控制消息协议(ICMP)等,IP提供的一种不可靠的服务;

(4)数据链路层:相当于OSI模型的下面两层,是TCP/IP协议与底层硬件设备的接口,负责网络层与物理设备之间的数据交换,在TCP/IP中主要为IP协议发送和接受数据。它隐藏了物理设备的实现细节,网络层只是简单地数据交给接口层/或从接口层接收数据,并不关心物理层的网络形式,网络接口层通常是在设备驱动程序或网络接口卡中实现的;

OSI与TCP/IP模型对应关系

应用层: HTTP、FTP、POP3、Telnet、DNS、DHCP;

传输层:TCP、UDP;

网络层:IP;

数据链路层(接口层):PPP、以太网驱动、GPRS;

网络编程基础

TCP协议

TCP协议是一种面向连接的,可靠的,基于字节流的传送层通信协议。

类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。

UDP协议

UDP是一种无连接的传送层协议,提供不可靠信息传输服务。

UDP类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。

socket介绍

Socket是处在应用层与其下三层之间的一组抽象接口。是操作系统提供的一组编程API,它把复杂的TCP/IP协议簇封装在Socket接口后面,对用户来说,一组简单的接口就是全部。让socket去组织数据,以符合指定的协议。

目前实际应用的Windows Sockets 规范主要有1.1版和2.0版,其中1.1版只支持TCP/IP协议,而2.0版支持多协议,并具有良好的向后兼容性。

使用windows sockets的主要步骤:

1、加载WinSock库 WASStartup()

2、创建套接字 socket()

3、绑定套接字 bind()

4、开始监听 listen()

5、接收/发送处理 send()/recv()

6、接收客户的连接请求 accept()

7、关闭套接字 closesocket()

8、清理环境 WASclearend()

熟悉以上几个函数就可以做简单的网络编程了,下面是函数的使用说明。

加载WinSock函数 WSAStartup()

不管是客户端还是服务端,开发socket应用程序时,必须先加载windows sockets动态库,通常用WSAStartup函数实现此功能

//wVersionRequested参数用来指定想要加载winSock库的版本
WSAStartup (
_In_ WORD wVersionRequested,
_Out_ LPWSADATA lpWSAData
);

套接字函数 socket()

初始化WinSocket DLL后,创建套接字,socket函数和WSASocket函数将实现此功能。该函数调用成功后,返回一个新建的套接字句柄

SOCKET socket(
_In_ int af,
_In_ int type,
_In_ int protocol
); 所需要填入的参数:
af:
AF_INET //internet协议(IP V4)
AF_IRDA //红外协议
AF_BTH //蓝牙协议 type:
SOCK_STREAM //流式socket(使用TCP建立连接,通信过程可靠 TCP)
SOCK_DGRAM //数据报socket(无需建立连接,通讯过程不可靠 UDP)
SOCK_RAW //原始套接字,winsock接口并不使用某种特定的协议去封装它,而是由程序字形处理数据报和协议首部。

绑定套接字函数 bind()

bind()函数将套接字绑定到一个已知的地址上。

int bind(
_In_ SOCKET s, //套接字
_In_reads_bytes_(namelen) const struct sockaddr FAR * name, //地址结构体变量(IP,端口,协议簇)
_In_ int namelen //Sockaddr 结构长度
); 示例:
sockaddr_in servAddr;
servAddr.sin_family = AF_INET; //internet协议(IP V4)
servAddr.sin_port = htons((short)5566); //指定了TCP或UDP通信服务的端口号
//记得把地址修改成自己的IP
ervAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//sin_addr域用来存储32位IP地址。这个结构中包含一个联合结构体S_un, //3. 绑定套接字
int retVal = bind(listenSock, (sockaddr *)&servAddr, sizeof(sockaddr)); 端口号的选取范围:
0-1023由IANA管理,保留为公共服务使用;
1024-48151是普通用户注册的端口号;
49152-65535是动态的端口号;

监听函数 listen()

listen函数将套接字设置为监听模式。

int
WSAAPI
listen(
_In_ SOCKET s, //套接字
_In_ int backlog //能够同时连接的最大数
);

Backlog:

如果该参数为3表示等待连接的最大数值3,如果有4个客户端同时向服务器发出请求,那么前3个连接会被放在等待处理的队列中,以便服务器依次为他们服务,而第4个连接将会造成错误。当服务器接受了一个连接请求,这个请求就从队列中删除。

示例:
re = listen(listenSock, //套接字
SOMAXCONN); //能够同时连接的最大数

接收客户的连接请求 accept()

accept函数实现接收一个连接请求;

SOCKET
accept(
_In_ SOCKET s, //套接字
_Out_writes_bytes_opt_(*addrlen) struct sockaddr FAR * addr, //返回请求连接主机的地址
_Inout_opt_ int FAR * addrlen //sockaddr_in的大小
);

接收/发送处理 send()/recv()

recv()函数用于接收数据,send()函数用于发送数据,参数基本相同!

int
recv(
_In_ SOCKET s, //发送数据的套接字
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf, //接收数据缓冲区
_In_ int len, //缓冲区的大小
_In_ int flags //影响该函数的行为
); int
send(
_In_ SOCKET s, //发送数据的套接字
_In_reads_bytes_(len) const char FAR * buf, //接收数据缓冲区
_In_ int len, //缓冲区的大小
_In_ int flags //影响该函数的行为
); 示例:
//6. 发数据
char buf[512] = "hello I am message";
retVal = send(sToClientSock, buf, sizeof(buf), 0); //7. 收数据
char recvBuf[1024] = { 0 };
retVal = recv(sToClientSock, recvBuf, 1024, 0);

关闭套接字 closesocket()

closesocket()函数只有一个参数,就是要关闭套接字句柄。

int
WSAAPI
closesocket(
_In_ SOCKET s //发送数据的套接字
);

connect()函数

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。

int
connect(
_In_ SOCKET s, //套接字
_In_reads_bytes_(namelen) const struct sockaddr FAR * name, //地址结构体变量(IP,端口,协议簇)
_In_ int namelen //Sockaddr 结构长度
); 示例代码:
//3. 连接服务器 connect
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(short(1234));
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//记得修改为服务器IP
int retVal = connect(sToServerSock, (sockaddr*)&servAddr, sizeof(sockaddr));

TCP编程实例代码

TCP服务端代码:

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") /*
1. 初始化环境 wsastartup
2. 创建套接字 socket
3. 绑定套接字 bind
4. 监听套接字 listen
5. 处理套接字连接 accept
6. 发送接收数据 send/recv
7. 关闭套接字 closesocket
8. 清理环境 wsacleanup
*/
int main()
{
// 1. 初始化环境 wsastartup
WSADATA wsd = {0};
int re = WSAStartup(MAKEWORD(2,2),&wsd);
if (re != 0) { return 0; }
if (LOBYTE(wsd.wVersion) != 2||
HIBYTE(wsd.wVersion) != 2 )
{
return 0;
}
// 2. 创建套接字 socket
SOCKET listenSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (listenSock == INVALID_SOCKET)
{
return 0;
}
// 3. 绑定套接字 bind
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(0x5566);
serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.96");//自己的IP或者127.0.0.1 re = bind(listenSock,(sockaddr *)&serverAddr, sizeof(serverAddr));
if (re == SOCKET_ERROR)
{
printf("bind error");
goto over;
}
// 4. 监听套接字 listen
re = listen(listenSock, SOMAXCONN);
if (re == SOCKET_ERROR)
{
printf("listen error");
goto over;
}
// 5. 处理套接字连接 accept
sockaddr_in clientAddr;
int size = sizeof(clientAddr);
SOCKET sToClientSock = accept(
listenSock, (sockaddr *)&clientAddr,&size);
if (sToClientSock == INVALID_SOCKET)
{
printf("listen error");
goto over;
}
// 6.1 发送数据 send
char buf[100] = "hello socket client,from server!";
re = send(sToClientSock, buf, strlen(buf), 0);
if (re == SOCKET_ERROR)
{
goto over;
}
// 6.2 接收数据 recv
re = recv(sToClientSock, buf, sizeof(buf), 0);
if (re == INVALID_SOCKET)
{
goto over;
}
over:
// 7. 关闭套接字 closesocket
closesocket(listenSock);
closesocket(sToClientSock);
// 8. 清理环境 wsacleanup
WSACleanup(); return 0;
}

TCP客户端代码:

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") /*
1. 初始化环境 wsastartup
2. 创建套接字 socket
// 3. 绑定套接字 bind
// 4. 监听套接字 listen
// 5. 处理套接字连接 accept
3. 连接服务器 connect
4. X
5. X
6. 发送接收数据 send/recv
7. 关闭套接字 closesocket
8. 清理环境 wsacleanup
*/
int main()
{
// 1. 初始化环境 wsastartup
WSADATA wsd = { 0 };
int re = WSAStartup(MAKEWORD(2, 2), &wsd);
if (re != 0) { return 0; }
if (LOBYTE(wsd.wVersion) != 2 ||
HIBYTE(wsd.wVersion) != 2)
{
return 0;
}
// 2. 创建套接字 socket
SOCKET sToServSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sToServSock == INVALID_SOCKET)
{
return 0;
}
// 3. 绑定套接字 bind
// 4. 监听套接字 listen
// 5. 处理套接字连接 accept
//3. 连接服务器 connect
sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(0x5566);
servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.96");
re = connect(sToServSock, (sockaddr *)&servAddr, sizeof(servAddr));
if (re == SOCKET_ERROR)
{
goto over;
}
// 6. 发送接收数据 send/recv
char buf[100] = "hello socket server,from client!";
re = send(sToServSock, buf, strlen(buf), 0);
if (re == SOCKET_ERROR)
{
goto over;
}
re = recv(sToServSock, buf, sizeof(buf), 0);
if (re == INVALID_SOCKET)
{
goto over;
}
over:
// 7. 关闭套接字 closesocket
closesocket(sToServSock);
// 8. 清理环境 wsacleanup
WSACleanup(); return 0;
}

UDP编程

在UDP编程中,大部分函数一样;其中不同的地方为socket()函数创建套接字时需要指定数据报为UDP。发送与接收函数与TCP编程所使用的recv()和send()也有所不同;

接收数据 recvfrom()

recvfrom()函数用于接收数据,并且返回发送数据主机的地址。

注:函数的返回值,是接收到的大小。

int
recvfrom(
//用来接收数据的套接字
_In_ SOCKET s,
//接收数据的缓冲区
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
//接收缓冲区的长度
_In_ int len,
//最后一个参数通常为0,0表示无特殊行为;
_In_ int flags,
//接收的地址结构
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
//sockaddr结构的大小
_Inout_opt_ int FAR * fromlen
);

发送数据 sendto()

sendto()函数用于发送数据;

注:该函数成功则返回发送数据的字节数。

int
sendto(
//用来发送数据的套接字
_In_ SOCKET s,
//发送数据的缓冲区
_In_reads_bytes_(len) const char FAR * buf,
//要发送数据的长度
_In_ int len,
//一般为0
_In_ int flags,
//目标地址和端口
_In_reads_bytes_(tolen) const struct sockaddr FAR * to,
//sockaddr结构大小
_In_ int tolen
);

UDP编程实例代码

UDP服务端代码:

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") /*
1. 初始化环境 wsastartup
2. 创建套接字 socket
3. 绑定套接字 bind // 4. 监听套接字 listen
// 5. 处理套接字连接 accept
6. 发送接收数据 sendto/recvfrom 7. 关闭套接字 closesocket
8. 清理环境 wsacleanup
*/
int main()
{
// 1. 初始化环境 wsastartup
WSADATA wsd = { 0 };
int re = WSAStartup(MAKEWORD(2, 2), &wsd);
if (re != 0) { return 0; }
if (LOBYTE(wsd.wVersion) != 2 ||
HIBYTE(wsd.wVersion) != 2)
{
return 0;
}
// 2. 创建套接字 socket
SOCKET sToClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sToClientSock == INVALID_SOCKET)
{
return 0;
}
// 3. 绑定套接字 bind
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; //internet网络协议
serverAddr.sin_port = htons((short)0x5566); //端口号
serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.96");//自己的IP或者127.0.0.1 re = bind(sToClientSock, (sockaddr *)&serverAddr, sizeof(serverAddr));
if (re == SOCKET_ERROR)
{
printf("bind error");
goto over;
}
// 4. 监听套接字 listen
// 5. 处理套接字连接 accept
// TCP编程需要使用两个套接字,而UDP只需要一个,这是TCP的通信方式和UDP的方式决定的; // 6. 发送接收数据 sendto/recvfrom
char buf[100] = "hello socket client,from server!";
sockaddr_in clientAddr ;
int size = sizeof(clientAddr);
re = recvfrom(sToClientSock, buf, sizeof(buf), 0,
(sockaddr*) &clientAddr,&size);
if (re == INVALID_SOCKET)
{
goto over;
}
char buf2[100] = "hello socket client,from server!";
re = sendto(sToClientSock, buf2, strlen(buf2), 0,
(sockaddr*)&clientAddr,sizeof(clientAddr)); if (re == SOCKET_ERROR)
{
goto over;
} over:
// 7. 关闭套接字 closesocket
closesocket(sToClientSock);
closesocket(sToClientSock);
// 8. 清理环境 wsacleanup
WSACleanup(); return 0;
}

UDP客户端代码:

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") /*
1. 初始化环境 wsastartup
2. 创建套接字 socket
//3. 绑定套接字 bind // 4. 监听套接字 listen
// 5. 处理套接字连接 accept
6. 发送接收数据 sendto/recvfrom 7. 关闭套接字 closesocket
8. 清理环境 wsacleanup
*/
int main()
{
// 1. 初始化环境 wsastartup
WSADATA wsd = { 0 };
int re = WSAStartup(MAKEWORD(2, 2), &wsd);
if (re != 0) { return 0; }
if (LOBYTE(wsd.wVersion) != 2 ||
HIBYTE(wsd.wVersion) != 2)
{
return 0;
}
// 2. 创建套接字 socket
SOCKET sToClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sToClientSock == INVALID_SOCKET)
{
return 0;
}
// 3. 绑定套接字 bind
// 4. 监听套接字 listen
// 5. 处理套接字连接 accept // 6. 发送接收数据 sendto/recvfrom
char buf[100] = "hello socket client,from client!";
sockaddr_in clientAddr;
clientAddr.sin_family = AF_INET;
clientAddr.sin_port = htons(0x5566);
clientAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.96");//自己的IP或者127.0.0.1 int size = sizeof(clientAddr); re = sendto(sToClientSock, buf, strlen(buf), 0,
(sockaddr*)&clientAddr, sizeof(clientAddr)); if (re == SOCKET_ERROR)
{
goto over;
}
re = recvfrom(sToClientSock, buf, sizeof(buf), 0,
(sockaddr*)&clientAddr, &size);
if (re == INVALID_SOCKET)
{
goto over;
}
over:
// 7. 关闭套接字 closesocket
closesocket(sToClientSock);
// 8. 清理环境 wsacleanup
WSACleanup(); return 0;
}