网络套接字编程

网络套接字编程

注:本文为学习《C语言从入门到精通》时,对部分章节的总结

1、计算机网络基础

1.1、IP地址

IP地址由IP协议规定的32位的二进制数表示,最新的IPv6协议由128位表示。

32位的IP地址主要分为前缀和后缀两部分。前缀表示计算机所属的物理网络,后缀确定该网络上的唯一一台计算机。在互联网上,每一个物理网络都有唯一地网络号,根据网络号的不同,可以将IP地址分为5类,即A类、B类、C类、D类和E类。A、B和C类属于基本类,D类用于多播发送,E类属于保留。

类型

 

A类

0.0.0.0~127.255.255.255

B类

128.0.0.0~191.255.255.255

C类

192.0.0.0~223.255.255.255

D类

224.0.0.0~239.255.255.255

E类

240.0.0.0~247.255.255.255

其中有几个特殊IP地址:

  1. 网络地址:在IP地址中主机地址为0的表示网络地址,如128.111.0.0
  2. 广播地址:在网络号后跟所有位全是1的IP地址,表示广播地址
  3. 回送地址:127.0.0.1表示回送地址,用于测试

1.2、OSI七层参考模型

层次

名称

功能描述

第7层

应用层(Application)

负责网络中应用程序与网络操作系统间的联系。

第6层

表示层(Presentation)

用于确定数据交换的格式,负责设备之间所需要的字符集合数据的转换

第5层

会话层(Session)

用户应用程序与网络层的接口,能够建立与其他设备的连接,即会话,并且能够对会话进行有效的管理

第4层

传输层(Transport)

提供会话层和网络层之间的传输服务,该服务会从会话层获得数据,必要时对数据进行分割,然后将数据无误的传递到网络层

第3层

网络层(Network)

能够将传输的数据封包,然后通过路由选择、分段组合的控制,将信息从源设备传送到目标设备

第2层

数据链路层(Data Link)

修正传输过程中的错误信号

第1层

物理层(Physical)

利用传输介质为数据链路层提供物理连接,规范了网络硬件的特性、规格和传输速度

1.3、地址解析

地址解析:将计算机的协议地址解析为物理地址,即MAC地址,又称为媒体访问控制地址。通常,在网络上由地址解析协议(ARP)来实现地址解析。

两台计算机通信过程:A主机IP:192.168.1.21   B主机IP:192.168.1.23

  1. 主机A从本地ARP缓存中查找IP为192.168.1.23对应的物理地址。可以用命令行“arp -a”查看本地ARP缓存
  2. 如果主机A在ARP缓存中没有发现192.168.1.23映射的物理地址,将发送ARP请求帧到本地网络上的所有主机,在ARP请求中包含了主机A的物理地址和IP地址
  3. 本地网络上的其他主机接收到ARP请求帧后,检查是否与自己的IP地址匹配,如果不匹配,则丢弃ARP请求。如果主机B发现与自己的IP地址匹配,则将主机A的物理地址和IP地址添加到自己的ARP缓存中,然后主机B将自己的物理地址和IP地址发送到主机A,当主机A接收到主机B发来的信息,将以这些信息更新ARP缓存
  4. 当主机B的物理地址确定后,主机A就可以与主机B进行通信了

1.4、域名系统

 

1.5、TCP/IP协议

TCP/IP协议:传输控制协议/网际协议

TCP/IP协议将网络分为4层

TCP/IP协议

OSI参考模型

应用层(包括Telnet、FTP、SNTP协议)

会话层、表示层和应用层

传输层(包括TCP、UDP协议)

传输层

网络层(包括ICMP、IP、ARP等协议)

网络层

数据链路层

物理层和数据链路层

TCP/IP协议是一个包含多种协议的协议簇,其中主要的有网际协议(IP)和传输控制协议(TCP)

1.TCP协议:是一种提供可靠数据传输的通用协议。是TCP/IP体系结构中传输层上的协议。在发送数据时,应用层的数据传输到传输层,加上TCP的首部,数据就构成了报文。报文是网际层IP的数据,再加上IP首部,就构成了IP数据报。TCP协议数据:

typedef struct HeadTCP {
    WORD SourcePort;  // 16位源端口号
    WORD DePort;      // 16位目的端口
    DWORD SequenceNo; // 32位序号
    DWORD ConfirmNo;  // 32位确认序号
    BYTE HeadLen;     // 与Flag为一个组成部分,首部长度,占4位,
    BYTE Flag;        // 保留6位,6位标识,共16位
    WORD WndSize;     // 16位窗口大小
    WORD CheckSum;    // 16位校验和
    WOED UrgPtr;      // 16位紧急指针
} HEADTCP;

2.IP协议:又称网际协议,工作在网络层,主要提供无链接数据报传输。不保证数据报的发送,但可以最大限度地发送数据。

IP协议数据:

typedef struct HeadIP {
    unsigned char headerlen:4; // 首部长度,占4位
    unsigned char version:4;   // 版本,占4位
    unsigned char servertype;  // 服务类型,占8位,即1个字节
    unsigned short totallen;   // 总长度,占16位
    unsigned short id;         // 与idoff构成标识,共占16位,
    unsigned short idoff;      // 前3位是标识,后13位是片偏移
    unsigned char ttl;         // 生存时间,占8位
    unsigned char proto;       // 协议,占8位
    unsigned short checksum;   // 首部检验和,占16位
    unsigned int sourceIP;     // 源IP地址,占32位
    unsigned int destIP;       // 目的IP地址,占32位
} HEADIP;

3.ICMP协议:又称网际控制报文协议。负责网络上设备状态的发送和报文检查,可以将某个设备的故障信息发送到其它设备上。

ICMP协议数据:

typedef struct HeadICMP {
    BYTE Type;   // 8位类型
    BYTE Code;   // 8位代码
    WORD ChkSum; // 16位检验和
} HEADICMP;

4.UDP协议:面向无连接的协议,为应用程序提供一次性的数据传输服务,不提供差错恢复,不能提供数据重传,所以安全性略差

UDP协议数据:

typedef struct HeadUDP {
    WORD SourcePort; // 16位源端口号
    WORD DePort;     // 16位目的端口
    WORD Len;        // 16位UDP长度
    WORD ChkSum;     // 16位UDP检验和
}HEADUDP;

1.6、端口

端口:一个16位的无符号整数值来表示的,通信的应用程序(进程)。围0~65535,低于256的端口被作为系统的保留端口,用于系统进程的通信,不在这范围的端口号被称为自由端口。

1.7、套接字的引入

套接字存在于通信区域(也称为地址族)中,主要用于间通过套接字通信的进程的公有特性综合在一起。套接字通常只与同一区域的套接字交换数据。

1.8、网络字节顺序

TCP/IP协议使用16位整数和32位整数的高位先存格式。

2、套接字基础

套接字是网络通信的基石,是网络通信的基本构件。

2.1、套接字概述

套接字实际上是一个指向传输提供者的句柄。可分为原始套接字、流式套接字和数据包套接字。

  1. 原始套接字:是在WinSock2规范中提出的,能够使程序开发人员对底层的网络传输机制进行控制,在原始套接字下接收的数据中包含IP头
  2. 流式套接字:提供双向、有序、可靠的数据传输服务。该类型套接字在通信前需要双方建立连接,TCP协议采用的就是流式套接字
  3. 数据包套接字:提供双向的数据流,但不能保证数据传输的可靠性、有序性、和无重复型。UDP协议采用的就是数据包套接字

2.2、TCP的套接字的socket编程

TCP是面向连接的可靠的传输协议。利用TCP协议进行通信时,首先要建立通信双方的连接。一旦连接建立完成,就可以进行通信。TCP提供了数据确认和数据重传的机制,保证了发送的数据一定能到达通信的对方。

基于TCP面向连接的socket编程的服务器端程序流程:

  1. 创建套接字socket
  2. 将创建的套接字绑定(bind)到本地的地址和端口上
  3. 设置套接字的状态位监听状态(listen),准备接受客户端的连接请求
  4. 接受请求(accpet),同时返回得到一个用于连接的新套接字
  5. 使用这个新套接字进行通信(通信函数使用send/recv)
  6. 通信完毕,释放套接字资源(closesocket)

基于TCP面向连接的socket编程的客户端程序流程:

  1. 创建套接字socket
  2. 向服务器发出连接请求(connect)
  3. 请求链接后与服务器进行通信操作(send/recv)
  4. 释放套接字资源(closesocket)

2.3、UDP的套接字的socket编程

UDP是无连接的不可靠的传输协议。采用UDP进行通行时,不需要建立连接,可直接向一个IP地址发送数据,但不能保证对方能收到。

基于UDP面向无连接的套接字编程来说,服务端和客户端概念不是很严格。可以把服务器称为接收端,客户端就是发送数据的发送端。

基于UDP面向无连接的socket编程的发送端程序流程:

  1. 创建套接字socket
  2. 将套接字绑定(bind)到一个本地地址和端口上
  3. 等待接收数据(recvfrom)
  4. 释放套接字资源(closesocket)

基于UDP面向无连接的socket编程的接收==接收端程序流程:

  1. 创建套接字socket
  2. 向服务器发送数据(sendto)
  3. 释放套接字资源(closesocket)

3、套接字函数

3.1、套接字函数介绍

1.WSAStartup函数:

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested:表示调用的Windows Socket版本,高字节记录修订版本,低字节记录主版本。版本为2.1,高字节记录1,低字节记录2

lpWSAData:是一个WSADATA结构指针

typedef struct WSAData {
    WORD wVersion;                              //调用者使用的WS2_32.DLL动态库的版本号
    WORD wHighVersion;                          //WS2_32.DLL支持的最高版本,通常与wVersion相同
    char szDescription[WSADESCRIPTION_LEN + 1]; //套接字的描述信息
    char szSystemStatus[WSASYS_STATUS_LEN + 1]; //系统的配置或状态信息
    unsigned short iMaxSockets;                 //最多可打开的套接字个数
    unsigned short iMaxUdpDg;                   //数据报的最大长度
    char FAR* lpVendorInfo;                     //套接字厂商信息
} WSADATA, FAR * LPWSADATA;

作用:初始化Ws2_32.dll动态链接库。在使用套接函数之前,一定要初始化Ws2_32.dll动态链接库。

2.socket函数

SOCKET socket(int af, int type, int protocol);

af:一个地址家族,通常为AF_INET

type:套接字类型。SOC_STREAM表示创建面向连接的流式套接字;SOCK_DGRAM表示创建面向无连接的数据报套接字;SOCK_RAW表示创建原始套接字

protocol:套接字所用的协议,如果不指定,可设置为0

返回值:创建的套接字句柄

功能:创建一个套接字

3.bind函数

int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);3

s:套接字标识

name:一个sockaddr结构指针,结构中包含了要结合的地址和端口号

namelen:确定name缓冲区的长度

返回值:函数执行成功,返回0;否则为SOCKET_ERROR

功能:将套接字绑定到指定的端口和地址上

4.listen函数

int listen(SOCKET s, int backlog);

s:套接字标识

backlog:表示等待连接的最大队列长度。比如值为2,此时有3个客户端同事发出连接请求,那么前两个连接会放置在等待队列中,第3个客户端会得到错误信息

功能:将套接字设置为监听模式。对于流式套接字,必须处于监听模式才能够接收客户端套接字的连接

5.accept函数

SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);

s:套接字标识,应处于监听状态

addr:sockaddr_in结构指针,包含一组客户端的端口号、IP地址等信息

addrlen:参数addr的长度

返回值:一个新的套接字,对应于已经接收的客户端连接,对于客户端的所有后续操作,都应使用这个套接字

功能:接收客户端的连接。流式套接字必须处于监听状态才能接收客户端的连接

6.closesocket函数

int closesocket(SOCKET s);

s:套接字标识。如果参数s设置了SO_DONTLINGER选项,则调用该函数后会立即返回。此时如果有数据尚未传送完毕,则会继续传递数据,然后再关闭套接字

功能:关闭套接字

7.connect函数

int connect(SOCKET s, const struct sockaddr FAR* name, int namelen);

s:套接字标识

name:套接字s要连接的主机地址和端口号

namelen:name参数的缓冲区长度

返回值:函数执行成功,返回0;否则返回SOCKET_ERROR。用户可以通过WSAGETLASTERROR得到其错误描述

功能:发送一个连接请求

8.htons函数

u_short htons(u_short hostshort);

hostshort:一个主机排列方式的无符号短整型数据

返回值:16位的网络排列方式数据

功能:将一个16位无符号短整形数据由主机排列方式转换为网络排列方式

9.htonl函数

u_long htonl(u_long hostlong);

hostlong:一个主机排列方式的无符号长整型数据

返回值:32位的网络排列方式数据

功能:将一个32位无符号长整形数据由主机排列方式转换为网络排列方式

10.inet_addr函数

unsigned long inet_addr(const char FAR* cp);

cp:一个IP地址的字符串

返回值:32位无符号长整数

功能:将一个由字符串表示的地址转换为32位的无符号长整形数据

11.recv函数

int recv(SOCKET s, char FAR* buf, int len, int flags);

s:套接字标识

buf:接收数据的缓冲区

len:buf的长度

flags:函数的调用方式。MSG_PEEK:查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走;MSG_OOB:用来处理Out-Of-Band数据,也就是外带数据

功能:从面向连接的套接字中接收数据

12.send函数

int send(SOCKET s, char FAR* buf, int len, int flags);

s:套接字标识

buf:存放要发送数据的缓冲区

len:buf的长度

flags:函数的调用方式

功能:在面向连接的套接字间发送数据

13.recvfrom函数

int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockeraddr FAR* from, int FAR* fromlen);

s:准备接收数据的套接字

buf:接收数据的缓冲区

len:buf长度

flags:通过设置该值可以影响函数的调用行为

from:指向地址结构的指针,用来接收发送数据方的地址信息

fromlen:from的长度

功能:用于接收一个数据报信息并保存源地址

14.sendto函数

int sendto(SOCKET s, char FAR* buf, int len, int flags, const struct sockeraddr FAR* to, int FAR* tolen);

s:准备接收数据的套接字

buf:要发送数据的缓冲区

len:buf长度

flags:通过设置该值可以影响函数的调用行为

to:指定目标套接字的地址

tplen:to的长度

功能:用于接收一个数据报信息并保存源地址

15.WSACleanup函数

int WSACleanup(void);

功能:释放为Ws2_32.dll动态链接库初始化时分配的资源

3.2、基于TCP的网络聊天程序

// 网络聊天服务端的程序
#include <stdio.h>
#include <windock.h>

int main()
{
    //定义变量
    char sendBuf[100];      //发送数据的buf
    char receiveBuf[100];   //接受数据的buf
    int sendLen;            //发送数据的长度
    int receiveLen;         //接收数据的长度
    int length;             //表示SOCKADDR的大小

    SOCKET socketServer;    //定义服务器套接字
    SOCKET socketReceive;   //定义用于连接套接字

    SOCKADDR_IN serverAdd;  //服务器地址信息结构
    SOCKADDR_IN clientAdd;  //客户端地址信息结构

    WORD wVersionRequested; //字(word):unsigned short
    WSADATA wsaData;        //库版本信息结构
    int error;              //表示错误

    //初始化套接字库
    //定义版本类型。将两个字节组合成一个字,前面是低字节,后面是高字节
    wVersionRequested = MAKEWORD(2, 2);
    //加载套接字库,初始化Ws2_32.dll动态链接库
    error = WSAStartUp(wVersionRequested, &wsaData);
    if (error != 0) {
        printf("加载套接字失败!\n");
        return 0;
    }
    //判断请求加载的版本号是否符合要求
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        WSACleanup(); //不符合,关闭套接字库
        return 0;
    }

    //设置连接地址
    serverAdd.sin_family = AF_INET; //地址家族,必须是AF_INET,注意只有它不是网络字节顺序
    serverAdd.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //主机地址
    serverAdd.sin_port = htons(5000); //端口号

    //创建套接字
    //AF_INET表示指定地址族,SOCK_STREAM表示流式套接字TCP,特定的地址家族相关的协议
    socketServer = socket(AF_INET, SOCK_STREAM, 0);

    //绑定套接字到本地的某个地址和端口上
    if (bind(socketServer, (SOCKADDR*)&serverAdd, sizeof(SOCKADDR)) == SOCKET_ERROR) {
        printf("绑定失败!\n");
    }

    //设置套接字位监听状态
    if (listen(socketServer, 5) < 0) {
        printf("监听失败!\n");
    }

    //接受连接
    length = sizeof(SOCKADDR);
    //接收客户端的发送请求,等待客户端发送connect请求
    socketReceive = accept(socketServer, (SOCKADDR*)&clientAdd, &length);
    if (socketReceive == SOCKET_ERROR) {
        printf("接受连接失败!\n");
    }

    //进行聊天
    while (1) {
        //接收数据
        receiveLen = recv(socketReceive, receiveBuf, 100, 0);
        if (receiveLen < 0) {
            printf("接收失败,程序退出!\n");
            break;
        } else {
            printf("客户端说:%s\n", receiveBuf);
        }

        //发送数据
        printf("请发送信息:\n");
        scanf("%s", sendBuf);
        sendLen = send(socketReceive, sendBuf, 100, 0);
        if (sendLen < 0) {
            printf("发送失败!\n");
        }
    }

    //释放套接字,关闭动态库
    closesocket(socketReceive); //释放客户端的套接字资源
    closesocket(socketServer);  //释放套接字资源
    WSACleanup();               //关闭动态链接库
    return 0;
}
// 网络聊天客户端的程序
#include <stdio.h>
#include <windock.h>

int main()
{
    //定义变量
    char sendBuf[100];      //发送数据的buf
    char receiveBuf[100];   //接受数据的buf
    int sendLen;            //发送数据的长度
    int receiveLen;         //接收数据的长度

    SOCKET socketSend;      //定义套接字
    SOCKADDR_IN serverAdd;  //服务器地址信息结构

    WORD wVersionRequested; //字(word):unsigned short
    WSADATA wsaData;        //库版本信息结构
    int error;              //表示错误

    //初始化套接字库
    //定义版本类型。将两个字节组合成一个字,前面是低字节,后面是高字节
    wVersionRequested = MAKEWORD(2, 2);
    //加载套接字库,初始化Ws2_32.dll动态链接库
    error = WSAStartUp(wVersionRequested, &wsaData);
    if (error != 0) {
        printf("加载套接字失败!\n");
        return 0;
    }
    //判断请求加载的版本号是否符合要求
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        WSACleanup(); //不符合,关闭套接字库
        return 0;
    }

    //设置服务器地址
    serverAdd.sin_family = AF_INET; //地址家族,必须是AF_INET,注意只有它不是网络字节顺序
    serverAdd.sin_addr.S_un.S_addr = inet_addr("192.168.1.43"); //主机地址
    serverAdd.sin_port = htons(5000); //端口号

    //进行连接服务器
    //AF_INET表示指定地址族,SOCK_STREAM表示流式套接字TCP,特定的地址家族相关的协议
    socketSend = socket(AF_INET, SOCK_STREAM, 0);

    //客户端发送connect请求
    if (connect(socketSend, (SOCKADDR*)&ServerAdd, sizeof(SOCKADDR)) == SOCKET_ERROR) {
        printf("连接失败!\n");
    }

    //进行聊天
    while (1) {
        //发送数据
        printf("请发送信息:\n");
        scanf("%s", sendBuf);
        sendLen = send(socketSend, sendBuf, 100, 0);
        if (sendLen < 0) {
            printf("发送失败!\n");
        }

        //接收数据
        receiveLen = recv(socketSend, receiveBuf, 100, 0);
        if (receiveLen < 0) {
            printf("接收失败,程序退出!\n");
            break;
        } else {
            printf("服务器端说:%s\n", receiveBuf);
        }


    }

    //释放套接字,关闭动态库
    closesocket(socketSend); //释放套接字资源
    WSACleanup();            //关闭动态链接库
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值