• windows socket 网络编程基本知识(中 下 )


    下面介绍网络7层协议在WINDOWS的实现:

    7层协议 WIN系统
    ________________________________________
    7 应用层 7 应用程序
    ________________________________________________
    6 表示层 6 WINSOCK API(DLL)
    ___________________________________________
    5 会话层 5 SPI(DLL)
    __________________________________________________
    4 传输层 4 TDI(VXD,SYS)
    ___________________________________________________
    3 网络层 3 NDIS(VXD,SYS)
    __________________________________________________
    2 数据链路层 2 网卡驱动程序(VXD,SYS)
    ___________________________________________
    1 物理层 1 网卡
    _________________________________________________
    相信这个映射图可以让大家比较清楚了解他们的对应关系


    TCP协议图示

    应用程序协议 HTTP FTP TELNET
    传输协议 TCP UDP
    网际协议 IP
    物理层协议 网卡

    IP协议保证数据的传输,TCP协议保证数据传输的质量.
    TCP/IP协议基于四层结构:应用层,传输层,网络层,接口层,数据在传输时每通过一层就要在数据上加个头,其中的数据供接受端同层使用,在
    接收端,每经过一层就把头去掉,来保证传输数据格式的一致.


    TCP头部结构:
    16位源端口号 16位目的端口号
    _______________________________________________________________________________
    32位序列号
    ___________________________________________________________________________
    32位确认号
    _____________________________________________________________________________________
    4位首部长度+6位保留字 6位标志 16位窗口大小
    _______________________________________________________________________________________
    16位效验和 16位紧急数据偏移量
    _____________________________________________________________________________________
    数据段
    _______________________________________________________________________________


    IP头部结构:
    4位IP版本号 4位首部长度 8位服务类型 16位总长度
    ___________________________________________________________________________________________
    16位标示 3位标志和偏移
    __________________________________________________________________________
    8位生存时间 8位协议 16位IP首部效验和
    _________________________________________________________________________________________
    32位源IP地址
    ___________________________________________________________________________________________
    32位目的IP地址
    ________________________________________________________________________________________
    TCP头和数据

    ____________________________________________________________________________
    第四节 关于服务器和客户端编程


    在网络编程中,最常用和最基础的就是WINSOCK. 现在我们讨论WINDOWS下的SOCKET编程.

    大凡在WIN32平台上的WINSOCK编程都要经过下列步骤:
    定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创建套接字->设置套接字选项->关闭套接字>卸载WINSOCK库->释放资源

    下面介绍WINSOCK C/S的建立过程:

    服务器 客户端
    ________________________________________________
    1 初始化WSA 1 初始化WSA
    ____________________________________________________
    2 建立一个SOCKET 2 建立一个SOCKET
    _____________________________________________________
    3 绑定SOCKET 3 连接到服务器
    _____________________________________________________
    4 在指定的端口监听 4 发送和接受数据
    _____________________________________________________
    5 接受一个连接 5 断开连接
    ______________________________________________________-
    6 发送和接受数据
    ___________________________________________________
    7 断开连接
    __________________________________________________

    大家注意,在VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以
    可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件).
    使用方式如下:
    #include
    #pragma comment(lib,"ws2_32.lib")

    大家注意,VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以
    可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件).
    使用方式如下:
    #include
    #pragma comment(lib,"ws2_32.lib")

    下面我们通过具体的代码演示服务器和客户端的工作流程:

    首先,建立一个WSADATA结构,通常用wsaData
    WSADATA wsaData;

    然后,调用WSAStartup函数,这个函数是连接应用程序与winsock.dll的第一个调用.其中,第一个参数是WINSOCK 版本号,第二个参数是指向
    WSADATA
    的指针.该函数返回一个INT型值,通过检查这个值来确定初始化是否成功.调用格式如下:WSAStartup(MAKEWORD(2,2),&wsaData),其中
    MAKEWORD(2,2)
    表示使用WINSOCK2版本.wsaData用来存储系统传回的关于WINSOCK的资料.

    if(iResuit=WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
    {
    printf("WSAStartup failed:%d",GetLastError()); //
    返回值不等与0,说明初始化失败
    ExitProcess(); //
    退出程序
    }

    应用程序在完成对请求的SOCKET库使用后,要调用WSACleanup函数来接触SOCKET库的绑定,并且释放资源.

    注意WSAStartup初始化后,必须建立一个SOCKET结构来保存SOCKET句柄.

    下面我们建立一个SOCKET.

    首先我们建立一个m_socketSOCKET句柄,接着调用socket()函数,函数返回值保存在m_socket.我们使用AF_INFE,SOCK_STREAM,IPPROTO_TCP
    三个参数.第一个表示地址族,AF_INFE表示TCP/IP,第二个表示服务类型,WINSOCK2,SOCKET支持以下三种类型;

    SOCK_STREAM
    流式套接字
    SOCK_DGRAM
    数据报套接字
    SOCK_RAW
    原始套接字

    第三个参数表示协议:

    IPPROTO_UDP UDP
    协议 用于无连接数据报套接字
    IPPROTO_TCP TCP
    协议 用于流式套接字
    IPPROTO_ICMP ICMP
    协议用于原始套接字

    m_socket=socket(AF_INFE,SOCK_STREAM,IPPROTO_TCP); //
    创建TCP协议

    以下代码用于检查返回值是否有错误:

    if(m_scoket==INVALID_SOCKET)
    {
    prinrf("Error at socket():%d\n",GetLastError());
    WSACleanup(); //
    释放资源
    return;
    }
    说明,如果socket()调用失败,他将返回INVALID_SOCKET.


    为了服务器能接受一个连接,他必须绑定一个网络地址,下面的代码展示如何绑定一个已经初始化的IP和端口的Socket.客户端程序用这个
    IP
    地址和端口来连接服务器.

    sockaddr_in service;
    service.sin_family=AF_INET; //INTERNET
    地址族
    service.sin_addr.s_addr=inet_addr("127.0.0.1"); //
    将要绑定的本地IP地址
    service.sin_port=htons(27015); //27015
    将要绑定的端口


    下面我们调用BIND函数,SOCKETSOCKADDR以参数的形式传入,并检查错误.

    if(bind(m_socket,(SOCKADDR*)&SERVICE,sizeof(service))==SOCKET_ERROR)
    {
    printf("bind() failed.\n");
    closesocket(m_socket);
    return;
    }

    当绑定完成后,服务器必须建立一个监听队列,以接受客户端的请求.listen()使服务器进入监听状态,该函数调用成功返回0,否则返回
    SOCKET_ERROR.
    代码如下:

    if(listen(m_socket,1)==SOCKET-ERROR)
    {
    printf("error listening on socket.\n");
    }

    服务器端调用完LISTEN(),如果此时客户端调用CONNECT()函数,服务器端必须在调用ACCEPT().这样服务器和客户端才算正式完成通信程序的
    连接动作.

    一旦服务器开始监听,我们就要指定一个句柄来表示利用ACCEPT()函数接受的连接,这个句柄是用来发送和接受数据的表示.建立一个SOCKET句柄
    Socket AcceptSocket
    然后利用无限循环来检测是否有连接传入.一但有连接请求,ACCEPT()函数就会被调用,并且返回这次连接的句柄.

    printf("waitong for a client to connect...\n");
    while(1)
    {
    AcceptSocket=SOCKET_ERROR;
    while(AcceptSocket==SOCKET_ERROR)
    {
    AcceptSocket=accept(m_socket,NULL,NULL);
    }
    }


    下面看客户端端代码:

    sockaddr_in clientService;
    clientService.sin_family=AF_INET; //INTERNET
    地址族
    clientService.sin_addr.s_addr=inet_addr("127.0.0.1"); //
    将要绑定的本地IP地址
    clientService.sin_port=htons(27015); //27015
    将要绑定的端口

    下面调用CONNECT()函数:

    if ( connect( m_socket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR)
    {
    printf( "Failed to connect.\n" );
    WSACleanup();
    return;
    } //
    如果调用失败清理退出
    //
    调用成功继续读写数据

    _____________________________________________________________________________________
    到这里,服务器和客户端的基本流程介绍完毕,下面我们介绍数据交换.


    send():
    int send
    {
    SOCKET s, //
    指定发送端套接字
    const char FAR?*buf, //
    指明一个存放应用程序要发送的数据的缓冲区
    int len, //
    实际要发送的数据字节数
    int flags //
    一般设置为0
    };
    C/S
    都用SEND函数向TCP连接的另一端发送数据.

    recv():
    int recv
    {
    SOCKET s, //
    指定发送端套接字
    char FAR?*buf, //
    指明一个缓冲区 存放RECC受到的数据
    int len, //
    指明BUF的长度
    int flags //
    一般设置为0

    };
    C/S
    都使用RECV函数从TCP连接的另一端接受数据

    _______________________________________________________________________________________________


    下面将完整的程序代码提供如下,大家可直接编译运行


    首先看客户端的代码:

    #include
    #include
    #pragma comment(lib, "ws2_32.lib")
    void main() {

    //
    初始化 Winsock.
    WSADATA wsaData;
    int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
    if ( iResult != NO_ERROR )
    printf("Error at WSAStartup()\n");

    //
    建立socket socket.
    SOCKET client;
    client = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

    if ( client == INVALID_SOCKET ) {
    printf( "Error at socket(): %ld\n", WSAGetLastError() );
    WSACleanup();
    return;
    }

    //
    连接到服务器.
    sockaddr_in clientService;

    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" );
    clientService.sin_port = htons( 27015 );

    if ( connect( client, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) {
    printf( "Failed to connect.\n" );
    WSACleanup();
    return;
    }

    //
    发送并接收数据.
    int bytesSent;
    int bytesRecv = SOCKET_ERROR;
    char sendbuf[32] = "Client: Sending data.";
    char recvbuf[32] = "";

    bytesSent = send( client, sendbuf, strlen(sendbuf), 0 );
    printf( "Bytes Sent: %ld\n", bytesSent );

    while( bytesRecv == SOCKET_ERROR ) {
    bytesRecv = recv( client, recvbuf, 32, 0 );
    if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET ) {
    printf( "Connection Closed.\n");
    break;
    }
    if (bytesRecv < 0)
    return;
    printf( "Bytes Recv: %ld\n", bytesRecv );
    }

    return;
    }


    下面是服务器端代码:

    #include
    #include
    #pragma comment(lib, "ws2_32.lib")
    void main() {

    //
    初始化
    WSADATA wsaData;
    int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
    if ( iResult != NO_ERROR )
    printf("Error at WSAStartup()\n");

    //
    建立socket
    SOCKET server;
    server = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

    if ( server == INVALID_SOCKET ) {
    printf( "Error at socket(): %ld\n", WSAGetLastError() );
    WSACleanup();
    return;
    }

    //
    绑定socket
    sockaddr_in service;

    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr( "127.0.0.1" );
    service.sin_port = htons( 27015 );

    if ( bind( server, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
    printf( "bind() failed.\n" );
    closesocket(server);
    return;
    }

    //
    监听 socket
    if ( listen( server, 1 ) == SOCKET_ERROR )
    printf( "Error listening on socket.\n");

    //
    接受连接
    SOCKET AcceptSocket;

    printf( "Waiting for a client to connect...\n" );
    while (1) {
    AcceptSocket = SOCKET_ERROR;
    while ( AcceptSocket == SOCKET_ERROR ) {
    AcceptSocket = accept( server, NULL, NULL );
    }
    printf( "Client Connected.\n");
    server = AcceptSocket;
    break;
    }

    //
    发送接受数据
    int bytesSent;
    int bytesRecv = SOCKET_ERROR;
    char sendbuf[32] = "Server: Sending Data.";
    char recvbuf[32] = "";

    bytesRecv = recv( server, recvbuf, 32, 0 );
    printf( "Bytes Recv: %ld\n", bytesRecv );

    bytesSent = send( server, sendbuf, strlen(sendbuf), 0 );
    printf( "Bytes Sent: %ld\n", bytesSent );

    return;
    }
    本程序仅仅描述了同步的情况!

    第五节 多线程编程介绍

    对于多线程的基本概念,我不在赘述,是个只要学习过一门编程语言就应该多进程和线程有个基本的了解.这里重点介绍一下如何实现多线程.

    通常一个程序的主线程有操作系统创建,如果想让其创建额外的线程,可以调用CreateThread()函数来完成.函数原形如下:

    HANDLE CreateThread()
    {
    LPSECURITY_ATTRIBUTES LPThreadAttributes, //
    指向SECURITY_ATTRIBUTES的指针
    SIZE_T dwStackSize, //
    表示线程为自己所用堆栈分配的地址空间的大小 系统缺省值为0
    LPTHREAD_START-TOUTINE lpStartAddress, //
    表示新线程开始执行时代码所在函数的地址 即线程函数名
    LPVOID lpParameter, //
    是传入线程函数的参数
    DWORD dwCreationFlags, //
    指定控制线程创建的附加标志 取0线程立即执行 取CREATE_SUSPENDED线程挂起
    LPDWORD lpThreadld //
    是个DWORD类型的地址,返回赋给该新线程的ID
    }

    线程函数lpParameter必须有以下原形:

    DWORD WINAPI XXXThreadFun(LPVOID lpParameter)
    {
    return(0);
    }

    ________________________________________________________________________________________
    下面我们来创建一个线程:

    #include
    #include
    DWORD WINAPI ThreadFunc( LPVOID lpParam ) //
    线程函数,跟普通的函数没什么两样
    {
    printf( "Parameter = %d.", *(DWORD*)lpParam );
    return 0;
    }

    VOID main( VOID )
    {
    DWORD dwThreadId, dwThrdParam = 1;
    HANDLE hThread;
    hThread = CreateThread( NULL,0,ThreadFunc,&dwThrdParam, 0,&dwThreadId);
    if (hThread == NULL)
    {
    printf( "CreateThread failed (%d)\n", GetLastError() );
    }
    else
    {
    _getch();
    CloseHandle( hThread );
    }
    }

    关于线程同步的问题,这里就不再讲解,请大家自己查阅资料,不查阅以后可能会有困难啊.培养一下各位的自己动手能力.

  • 相关阅读:
    tomcat https 启用8443加证书
    深刻理解Python中的元类metaclass(转)
    为什么数据科学家们选择了Python语言?
    谷歌如何管理世界上最聪明的工程师(转)
    前百度首席科学家张栋:36岁以前做到这8点再谈梦想(转)
    MySQL索引原理及慢查询优化(转)
    地理空间距离计算优化(转)
    Innodb中的事务隔离级别和锁的关系(转)
    关于大型网站技术演进的思考(转)
    应用引擎BAE3.0(转)
  • 原文地址:https://www.cnblogs.com/cy163/p/1562973.html
Copyright © 2020-2023  润新知