网络编程基础第二讲.网络编程框架
一丶了解的知识
1.什么是socket
socket 是开发接口.是TCP/IP网络环境下.应用程序与驱动程序之间访问的接口.
2.服务跟类型
socket服务 分为面向连接跟无连接,代表的协议就是TCP/IP
socket类型: 有三种类型
SOCK_STREAM 流式套接字. 可靠的套接字.可以处理大量数据.不会丢包.但是开销比大. 代表就是TCP协议. 是在传输层上做的.
SOCK_DGRAM 数据报套接字.不可靠.允许丢包. 常用与音频.视频等等. 代表就是UDP协议.
SOCK_RAW 原始套接字. 是在网络层进行编程的.也就是对底层的IP可以进行编程.不过常用的就是前边两种.
3.构建Windows框架.
4.IP地址的表现形式.
5.编写一个简单的网络程序. TCP模型.
二丶构建Windows框架
在windows下使用socket需要使用windows初始化函数.还要包含库文件.
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
以及使用API进行初始化
WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); WSAStartup(版本号,执行成功会返回一个WSADATA结构)
版本号使用MAKEWORD即可.我们现在使用的是2版本. 其实就是 高位低位都为2 0x0202这个值.
返回值:
0成功
1失败;
完整代码:
#include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib") int main() { WSADATA data; if (WSAStartup(MAKEWORD(2, 2), &data)) { puts("对不起初始化失败\r\n"); system("pause"); } getchar(); return 0; }
三丶IP地址表现形式
IP地址常用点分法来表示 也就是用点隔开. 用4个 0到255之间的整数来表示
例如:
192.168.0.0.1
但是在计算机中.不使用 点分法来保存IP地址. 这样会浪费存储空间.而且.不便计算 子网掩码.
我们知道 子网掩码 跟IP地址是and的关系. 所以不使用这个.
1.网络字节序
在网络传送中,IP地址会保存为32位的二进制数.
低位存储地址中保存数据的高位字节.高位中存储低位字节. 也就是我们常说的大端模式.
小端模式:高地址存储高位,低地址存储低位
0x12345678 地址放高位. 高地址放低位 例如: 78 56 34 12 我们的高地址存储了12 依次类推.
大端模式:
高地址存放低位, 低地址存放高位.
0x 12345678 在内存中表现形式 12 34 56 78
这个就是大端模式.
VC中 in_addr 来保存IP地址. 对应的转化则是 inet_addr 跟 inet_ntoa
示例代码如下:
in_addr addr; addr.S_un.S_addr = inet_addr("127.0.0.1"); //保存我们的IP地址.需要进行转换.
typedef struct in_addr { union { struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b; s_b1 ,s_b2 就是保存着我们的IP地址.点分法表示. struct { USHORT s_w1,s_w2; } S_un_w; ULONG S_addr; } S_un;
转换回来:
使用 inet_ntoa(addr)即可.
char *pszIp = NULL; pszIp = inet_ntoa(addr);
代码调试截图:
2.主机字节顺序
什么是主机字节顺序.主机字节顺序就是指不同的主机在堆IP地址进行存储的时候.使用的格式不同.
所以需要通过函数进行转换.
htonl() 将主机字节顺序格式的IP地址转化成为TCP/IP网络字节顺序
htons 主机转网络.
ntohl 网络转主机
ntohs 网络转主机.
h 主机的意思 to 转化的意思 n 网络的意思 network l 就是 ulong
所以根据缩写就能明白什么意思.
四丶客户端服务端编写流程以及代码
如下图可以清楚看到顺序
有点复杂的就是服务端(主机).他要绑定本地.并且接受连接.也就是说客户端只要连接.就会有一个Socket
我们对这个socket操作.就能实现主机跟客户端的通信了.
当有多个客户端连接的时候.我们就要保存socket了. 这个以后会讲到.也就是可能会接触到各种各样的模型.
来进行处理.
需要用到的函数解析
1.socket 创建socket
socket (协议家族,套接字类型,连接类型)
示例代码:
socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)
返回一个SOCKET值. windows中定义的是 UINT
失败: INVALID_SOCKET
2.绑定
sockaddr_in addrserver; addrserver.sin_family = AF_INET; addrserver.sin_port = htons(8564);//端口必须转换 addrserver.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//给定IP.如果是这个宏.则可以任何主机都可以连接 bind(hServer, (sockaddr *)&addrserver, sizeof(addrserver));
绑定的时候.我们要指明绑定的IP.以及绑定的端口.
此时我们会使用一个 sockaddr_in 的结构体.socket中其实真正的结构体是 sockaddr 但是操作不方便.
所以给了一个sockaddr_in的结构体. 大小跟 sockaddr结构体是一样的.
因为使用的是网络字节序. 所以 我们的端口以及IP地址都需要进行转换.
IP地址是 32位的.所以使用 htonl
端口是2个字节.也就是16为. 所以使用 htons
3.监听套接字
nRet = listen(hServer, 10); //参数1.socket 参数2. 同时监听处理的socket的数量 if (SOCKET_ERROR == nRet) { printf("listen 失败\r\n"); closesocket(hServer); WSACleanup(); goto OPT; }
监听就很简单了.我们要指明同时接受的socket以及同时处理的最大socket.所以给定socket 然后给一个数量即可.
4.获取用户的连接请求
获取用户的连接请求主要是用accept关键字. 给一个socket句柄.然后会把客户端连接这个socket的一系列信息都会记录在结构体中.
并且返回客户端的socket.我们只要对这个socket操作.就能进行操作了.
SOCKET hClient; sockaddr_in addrClient; //保存客户端的addr属性.客户端的IP以及端口都会放在里面 int nLen = sizeof(addrClient); hClient = accept(hServer, (sockaddr *)&addrClient, &nLen);传出结构.以及长度.返回客户端socket
5.对客户端socket进行读取或者写入.
读取的 API recv
写入的API send
相应的如果是UDP连接则使用 recvfrom /sendto
char pszBuffer[1024] = { NULL }; recv(hClient, pszBuffer, sizeof(char) * 1024, 0);
要对客户端的socket进行读写.所以传入的是客户端的socket
send是一样的.
最后进行关闭套接字即可.
服务端完整代码:
// socket.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib") int main() { WSADATA data; if (WSAStartup(MAKEWORD(2, 2), &data)) { puts("对不起初始化失败\r\n"); system("pause"); } in_addr addr; addr.S_un.S_addr = inet_addr("127.0.0.1"); char *pszIp = NULL; pszIp = inet_ntoa(addr); SOCKET hServer; hServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == hServer) { printf("socket 失败\r\n"); goto OPT; } //2.绑定在本地 sockaddr_in addrserver; addrserver.sin_family = AF_INET; addrserver.sin_port = htons(8564);//端口必须转换 addrserver.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//给定IP.如果是这个宏.则可以任何主机都可以连接 int nRet = bind(hServer, (sockaddr *)&addrserver, sizeof(addrserver)); if (SOCKET_ERROR == nRet) { printf("bind 失败\r\n"); goto OPT; } //3监听套接字的连接 nRet = listen(hServer, 10); //参数1.socket 参数2. 同时监听处理的socket的数量 if (SOCKET_ERROR == nRet) { printf("listen 失败\r\n"); closesocket(hServer); WSACleanup(); goto OPT; } //4.获取用户的数据请求.也就是接受客户端的连接的socket SOCKET hClient; sockaddr_in addrClient; //保存客户端的addr属性.客户端的IP以及端口都会放在里面 int nLen = sizeof(addrClient); hClient = accept(hServer, (sockaddr *)&addrClient, &nLen); //5.接受用户发送的数据 char pszBuffer[100] = { NULL }; recv(hClient, pszBuffer, sizeof(char) * 100, 0); //阻塞读取.不读取不返回 printf("接受到的数据 = %s \r\n", pszBuffer); OPT: closesocket(hClient); //释放一切资源. closesocket(hServer); WSACleanup(); getchar(); return 0; }
客户端代码就很简单了.
需要用到一个 connect API. 也就是连接的API. 调用这个API则跟服务端进行连接. 服务端的accept就会返回这个socket.
完整代码.
// socket.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib") int main() { WSADATA data; if (WSAStartup(MAKEWORD(2, 2), &data)) { puts("对不起初始化失败\r\n"); system("pause"); } //1.创建 SOCKET hClient; hClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == hClient) { printf("socket 失败\r\n"); goto OPT; } //2.连接 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //指定IP跟端口才能连接 addr.sin_port = htons(8564); connect(hClient, (sockaddr *)&addr, sizeof(addr));//指明服务端信息.才能进行通讯. //3.收发数据. char pszBuffer[100] = "HelloWorld"; send(hClient, pszBuffer, sizeof(char) * 100,0); OPT: closesocket(hClient); //释放一切资源. WSACleanup(); getchar(); return 0; }
应用实现截图:
课堂代码:
链接:https://pan.baidu.com/s/1iKX4cvFdQ9yAnZaIydrQIg 密码:9d0t