欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【面经笔记】网络编程

程序员文章站 2022-07-08 22:53:19
...

OSI(open system iterconnection)七层模型

  • 应用层

  • 表示层

进行统一的网络数据格式与某个计算机或软件特有的数据格式之间的转换

  • 会话层

负责建立、断开通信的连接

  • 传输层

管理两个节点的数据传输,负责可靠传输,确保数据被可靠地传送到目标地址

  • 网络层

寻址和路由选择

  • 数据链路层

  • 物理层


计算机网络中不同层有用到哪些协议?

  • 应用层:
    SSH、HTTP、HTML、TELENT、POP、、、

  • 传输层:
    DNS、TCP、UDP、UDP-Lite、SCTP、DCCP

  • 网络层:
    IP、ARP、ICMP、、

  • 网络接口:
    以太网、无线LAN、PPP、、、


计算机网络中不同层有用到哪些协议?

  • 应用层:

远程登录协议Telnet、文件传输协议FTP、超文本传输协议HTTP、域名服务DNS等

  • 传输层:

TCP:面向连接的可靠的传输协议。建立连接需要三次握手,提供了数据确认和数据重传的机制。应用在数据完整性要求高的场景

UDP:无连接的、不可靠的传输协议。不需要建立连接,可以直接向一个IP地址发送数据。应用在实时性要求高的场景。

  • 网络层

网际协议IP、互联网控制报文协议ICMP、组管理协议IGMP。


TCP/IP模型

  • 应用层
  • 传输层
  • 网络层
  • 网络接口

传输层主要功能是让应用程序之间实现通信。计算机同时运行多个应用程序,使用端口号识别。

TCP负责数据的可靠性,IP负责数据的送达。


TCP

IP首部中有一个协议字段,标识上一层是使用哪一种传输协议:TCP 或 UDP

TCP通过端口号判断上一层是什么应用程序


IP

IP是实现多个数据链路之间通信的协议,IP面向无连接

IP地址由网络标识主机标识两个部分组成。

网络标识必须保证相互连接的每个段的地址不重复。
而相同的段内相连的主机必须具有相同的网络地址。
主机标识则不允许在同一网段内重复出现。
(主机标识全置1即为广播地址)

网络标识:

子网掩码:对于IP地址网络标识部分全部为1,对于IP地址主机标识部分全部为0。

两种表示方式:

  • 分两行:IP:172.20.100.52 子网掩码:255.255.255.192
  • 只一行:172.20.100.52 /26 斜杠后为网络地址的位数

DNS:将网址字符串自动转换为IP地址


端口:抽象的软件结构(包括一些数据结构和I/O缓存区)。应用程序通过系统调用与某个端口连接后,传输层传给该端口的数据都能被相应的进程所接收,相应进程发送给传输层的数据都通过该端口输出。
1024以下的端口号保留给预定义的服务,应用程序需要指定1024以上的端口号。


网络字节顺序

网络协议规定接收到得第一个字节是高字节,存放到低地址(大端)

而不同平台进行通信时必须进行转换,不转换会造成错误的收发数据,字节序转换函数会根据当前平台的存储模式做出相应正确的转换,如果当前平台是大端,则直接返回不进行转换,如果当前平台是小端,会将接收到得网络字节序进行转换


套接字类型

  • 流式套接字(SOCK_STREAM)
    面向连接、可靠的数据传输服务 ,基于TCP协议实现

  • 数据报式套接字(SOCK_DGRAM)
    无连接服务,数据已独立包形式发送,基于UDP协议实现

  • 原始套接字(SOCK_RAW)


基于TCP的socket编程

参考:孙鑫 深入详解VC++ 第14章

服务端流程:

  1. 创建套接字(socket)
  2. 将套接字绑定到本地地址和端口上(bind)
  3. 将套接字设为监听模式(listen)
  4. 当请求到来后,接收连接请求,返回一个新的对应于此次连接的套接字(accept)
  5. 用返回的套接字与客户端通信(send/recv)
  6. 返回,等待下一个客户端请求
  7. 关闭套接字

客户端流程:

  1. 创建套接字(socket)
  2. 向服务器发送连接请求(connect)
  3. 与服务器通信(send/recv)
  4. 关闭套接字

注意,客户端不需要绑定操作(bind),有连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息,无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候打开端口。

http://blog.csdn.net/stpeace/article/details/45001255

tcp服务端必须有bind, 客户端通常不用bind, 当然如果你够无聊, 那也可以用一下bind. 在这里, 我要说一下了: 客户端用bind的程序很容易出问题, 你想想啊, 操作系统指定的不会冲突的随机端口难道不比你自己指定的容易冲突的固定端口好?

在很多场景下, 我们要在一个pc上开启多个客户端进程, 如果指定固定端口, 必然会造成端口冲突, 影响通信!所以, 我们就不要费力不讨好了, 客户端就不要指定端口了, 让操作系统来搞。这样,实际上就是操作系统对客户端的socket进行了隐式的命名(名称中的端口是随机的)。


基于UDP(面向无连接)的socket编程

服务端流程:

  • 创建套接字(socket)
  • 将套接字绑定到一个本地地址和端口上(bind)
  • 等待接收数据(recvfrom)
  • 关闭套接字

客户端流程:

  • 创建套接字(socket)
  • 向服务器发送数据(sendto)
  • 关闭套接字

为什么还要bind?为了完成通信,对于接收端来说,它必须先启动以接收客户端发送的数据,接收端必须告诉主机它是在哪个地址和端口上等待地址的到来。


API

  • wsastartup():

WSA(Windows Sockets Asynchronous,Windows异步套接字)的启动命令。
linux下不需要(很方便),直接可以使用。

  • socket()

创建套接字

  • bind()

绑定地址和端口

  • inet_addr()和inet_ntoa()

IP地址格式转换

inet_addr:将一个点分十进制的IP转换成一个长整数型数(u_long类型)。
如: inet_addr(“127.0.0.1”); //将字符串形式的IP地址转换为按网络字节顺序的整型值

inet_ntoa:将一个十进制网络字节序转换为点分十进制IP格式的字符串

  • listen()

将制定的套接字设置为监听模式

  • accept()

接收客户端发送的连接请求

  • send()

通过已建立连接的套接字发送数据

  • recv()

通过已建立连接的套接字接收数据

  • connect()

与特定的套接字建立连接

  • recvfrom()

将接收一个数据包信息并保存源地址

  • sendto()

向一个特定的目的方发送数据

  • htons()与htonl()

htons 将主机的无符号短整形数转换成网络字节顺序
htonl 将主机的无符号长整形数转换成网络字节顺序

htonl()–“Host to Network Long”

ntohl()–“Network to Host Long”

htons()–“Host to Network Short”

ntohs()–“Network to Host Short”


例:基于TCP程序编写

服务端:

#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>

void main()
{
    //加载套接字库
    WORD wVersionRequested;//保存winsock库的版本号
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1, 1);//指定加载的版本号

    err = WSAStartup(wVersionRequested, &wsaData);//加载库并获取库信息填至wsaData
    if (err != 0)
    {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return;
    }
    //创建监听的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);//流式套接字,自动选择协议

    //设置地址端口
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//套接字主机IP地址  字节顺序转换
    addrSrv.sin_family = AF_INET;//地址族
    addrSrv.sin_port = htons(6000);//分配的端口 字节顺序转换

    //套接字绑定地址端口
    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    listen(sockSrv, 5);//设置为监听模式,准备接收客户请求,等待连接队列的最大长度为5

    SOCKADDR_IN addrClient;//用于接收客户端地址信息
    int len = sizeof(SOCKADDR);//返回包含地址信息的长度
    while (1)
    {
        //等待客户请求到来
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
        char sendBuf[100];
        sprintf(sendBuf, "Welcome %s ", inet_ntoa(addrClient.sin_addr));

        //发送数据
        send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
        char recvBuf[100];
        //接收数据
        recv(sockConn, recvBuf, 100, 0);
        //打印接收的数据
        printf("%s\n", recvBuf);
        //关闭套接字
        closesocket(sockConn);
    }
    return ;
}

客户端:

// TcpClient.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>

void main()
{
    //加载套接字库
    WORD wVersionRequested;//保存winsock库的版本号
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1, 1);//指定加载的版本号

    err = WSAStartup(wVersionRequested, &wsaData);//加载库并获取库信息填至wsaData
    if (err != 0)
    {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return;
    }

    //创建套接字
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);//流式套接字,自动选择协议

    //设置地址端口
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本地回路IP地址  字节顺序转换
    addrSrv.sin_family = AF_INET;//地址族
    addrSrv.sin_port = htons(6000);//与服务器端口一致   字节顺序转换

    //向服务器发送连接请求
    connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    //接收数据
    char recvBuf[100];
    recv(sockClient, recvBuf, 100, 0);
    printf("%s\n", recvBuf);
    //发送数据
    send(sockClient, "This is lisi", strlen("This is lisi") + 1, 0);
    //关闭套接字
    closesocket(sockClient);
    WSACleanup();
    return;
}

运行结果:

【面经笔记】网络编程


相关标签: 网络编程