Windows下的网络编程——1、初识Socket(TCP)

WinSocket

一、

 

什么是Socket?

 

 

二、

根据底层协议Socket开发接口提供面向连接和无连接两种服务方式

 

 

允许小的丢包就用第二种方式,对应UDP协议

 

三、

 

 

 

 

四、IP地址的表现形式

1、网络传送中,IP地址被保存为32位,低位地址存储高位字节

 

2、不同主机对IP地址的存储使用的格式可能不一样,为此,我们需要明白主机与网络字节

 

的相互转化

 

 

五、socket编程所用到的一些函数或结构体

 

1、WSAStartup

 

  wsastartup()函数向操作系统说明,我们要用哪个库文件。 因此就可以将库文件与当前的

 

应用程序绑定,从而就可以调用该版本的socket的各种函数了。 一句话解释:wsastartup()

 

主要就是进行相应的socket库绑定。

 

2、WSACleanup

 

应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket

 

库的绑定并且释放Socket库所占用的系统资源。

 

 

 

 

3、int socket(int domain,int type, int protocol)

 

这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符(socket)

 

family指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;

type是套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;

protocol一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似。

 

4、bind函数

 

int bind ( int sockfd, struct sockaddr * addr, socklen_t addrlen )

返回: 0 ──成功, - 1 ──失败

 

参数sockfd

指定地址与哪个套接字绑定,这是一个由之前的socket函数调用返回的套接字。调用bind的函数之后,该套接字与一个相应的地址关联,发送到这个地址的数据可以通过这个套接字来读取与使用。

参数addr

指定地址。这是一个地址结构,并且是一个已经经过填写的有效的地址结构。调用bind之后这个地址与参数sockfd指定的套接字关联,从而实现上面所说的效果。

参数addrlen

正如大多数socket接口一样,内核不关心地址结构,当它复制或传递地址给驱动的时候,它依据这个值来确定需要复制多少数据。这已经成为socket接口中最常见的参数之一了。

 

功能:

在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和 端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用 户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由 bind的函数完成。

 

5、sockaddr_in

 

地址结构

 

6、AF_INET

Vc上的定义是这样的

#define AF_INET         2               /* internetwork: UDP, TCP, etc. */

 

7、listen函数:

int listen ( int sockfd,  int backlog )
返回: 0──成功,  - 1──失败

功能:

listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

 

第二个参数是同时处理的最大连接数

 

8、accept函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

 

(1)、功能

 

accept()系统调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET。它提取出所监听套接字的等待连接队列中第一个连接请求创建一个新的套接字,并返回指向该套接字的文件描述符。新建立的套接字不在监听状态,原来所监听的套接字也不受该系统调用的影响。

 

 

(2)、参数说明:

 

sockfd是由socket函数返回的套接字描述符,参数addr和addrlen用来返回已连接的对端进程(客户端)的协议地址。如果我们对客户端的协议地址不感兴趣,可以把arrdaddrlen均置为空指针

 

参数:

sockfd,    利用系统调用socket()建立的套接字描述符,通过bind()绑定到一个本地地址(一般为服务器的套接字),并且通过listen()一直在监听连接

addr,    指向struct sockaddr的指针,该结构用通讯层服务器对等套接字的地址(一般为客户端地址)填写,返回地址addr的确切格式由套接字的地址类别(比如TCP或UDP)决定;若addr为NULL,没有有效地址填写,这种情况下,addrlen也不使用,应该置为NULL;

备注:addr是个指向局部数据结构sockaddr_in的指针,这就是要求接入的信息本地的套接字(地址和指针)。

addrlen,    一个值结果参数,调用函数必须初始化为包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值;

备注:addrlen是个局部整形变量,设置为sizeof(struct   sockaddr_in)。

如果队列中没有等待的连接,套接字也没有被标记为Non-blocking,accept()会阻塞调用函数直到连接出现;如果套接字被标记为Non-blocking,队列中也没有等待的连接,accept()返回错误EAGAINEWOULDBLOCK

备注:一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;

若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;

若没有,就阻塞等待;

 

(3)、返回值

成功时,返回非负整数,该整数是接收到套接字的描述符;出错时,返回-1,相应地设定全局变量errno。

 

 

 

 

9、recv函数

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

第一个参数指定接收端套接字描述符;

 

第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

 

第三个参数指明buf的长度;

 

第四个参数一般置0。

 

10、send函数

与recv函数格式极其相似

11、connect函数(客户端)

 

int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

 

参数:
sockfd:标识一个套接字。
serv_addr:套接字s想要连接的主机地址和端口号。
addrlen:name缓冲区的长度。

 

 

 

 

六、

1、

 

 

可以看到图中第二行8888端口经过转化后变成了47138…  

 

所以说这个转化还是很有必要的

 

 七、服务端代码:

// 初识Socket.cpp : Defines the entry point for the console application.
//
//Serve端//
#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
/********************************/
/*                                */
/*            主机流程            */
/*1、建立流型套接字                */
/*2、套接字:与本地地址绑定        */
/*3、通知TCP,服务器准备好连接    */
/*4、等待客户机连接                */
/*5、建立连接                    */
/*6、在套接字上读写数据            */
/*7、关闭套接字                    */
/*8、关闭最初的套接字,服务结束    */
/*                                */
/********************************/

int main(int argc, char* argv[])
{

    WSADATA wsaData;//接受//
    WORD wVerSion = MAKEWORD(2,2);//版本号//
    if(WSAStartup(wVerSion,&wsaData))    //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0//
    {
        printf("WSAStartup\n");
        return 0;
    }
    /*
    实验...
    in_addr addr;

    addr.s_addr = inet_addr("192.168.0.1");//把ip地址转化成32位二进制数//  //s_addr是一个宏//
    char *lpszIp = inet_ntoa(addr);    //把二进制数转化成IP地址//
    */
    //主机字节顺序//
    //htonl    //主机字节顺序IP转TCP/IP网络字节顺序//
    //htons    //主机转网络u_short
    //ntohl    //网络转主机long//            //h相当于host//
    //ntohs    //网络转主机short//


    //1、建立流式套接字(TCP)////与指定服务提供者进行绑定//
    SOCKET hServer;
    hServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//PF_INET与AF_INET一样////1、用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族//  //2、套接字类型//   //3、套接字使用的协议//
    //这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符//
    if(hServer == INVALID_SOCKET)    //INVALID_SOCKET 不是有效的套接字//
    {
        printf("socket 失败\n");
    }

    //2、设置socket的服务地址//
    sockaddr_in addrServer; 
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(8888);
    addrServer.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY设置不同网卡的所有IP都能接受客户端的信息//

    //将套接字绑定在一个地址上(addrServer),并指定一个端口号//
     int nRet = bind(hServer,(sockaddr*)&addrServer,sizeof(addrServer));
    if(nRet == SOCKET_ERROR)
    {
        printf("socket绑定失败\n");
        closesocket(hServer);
        WSACleanup();        // 应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
        return 0;
    }
    //3、在socket上进行监听//
    nRet = listen(hServer,1);//同时处理的最大连接数//
    if(nRet == SOCKET_ERROR)
    {
        printf("socket绑定失败\n");
        closesocket(hServer);
        WSACleanup();
        return 0;
    }
    //4、获取用户数据请求//

    SOCKET hClient;    //处理用户的连接//
    sockaddr_in addrClient;    //这是
    int nLen = sizeof(addrClient);
    
    hClient = accept(hServer,(sockaddr*)&addrClient,&nLen);//参数addr和addrlen用来返回已连接的对端进程(客户端)的协议地址
    if(hClient    ==    INVALID_SOCKET)
    {
        printf("accept错误!\n");
        closesocket(hServer);
        WSACleanup();
        return 0;
    }
    

    //5、接受客户端数据,返回客户端数据//
    char szBuff[255];
    //循环接受客户端数据//
    while(1)
    {
        memset(szBuff,0,255);
        nRet = recv(hClient,szBuff,255,0);        //1、接受的客户端的句柄,2,3缓冲区的长度4、//
        if(nRet == SOCKET_ERROR)
        {
            printf("recv错误\n");
            closesocket(hServer);
            closesocket(hClient);
            WSACleanup();
            return 0;
        }

        char sPrintBuf[255];
        sprintf(sPrintBuf,"IP:%S,接受到的信息:%s",inet_ntoa(addrClient.sin_addr),szBuff);
        printf(sPrintBuf);
        if(strcmp(szBuff, "Close") == 0)
        {
            nRet = send(hClient,"Close",strlen("Close"),0);
            break;
        }
        else
        {
            sprintf(sPrintBuf,"已经接受到你的信息: %s",szBuff);
            nRet = send(hClient,sPrintBuf,strlen(sPrintBuf),0);
            if(nRet == SOCKET_ERROR)
            {
                printf("send出错\n");
                closesocket(hServer);
                closesocket(hClient);
                WSACleanup();
                return 0;
            }
        }
    }
    //6、释放socke//
        closesocket(hServer);
        closesocket(hClient);
        WSACleanup();

    printf("Hello World!\n");
    return 0;
}

 

八、 客户端代码

// : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")


/****************************/
/*                            */
/*        客户机流程            */
/*1、建立流式套接字            */
/*2、讲套接字与远程主机连接    */
/*3、在套接字上读/写数据    */
/*4、关闭套接字,结束会话    */
/*                            */
/****************************/



int main(int argc, char* argv[])
{
    int nRet;
    WSADATA wsaData;
    WORD wVerSion = MAKEWORD(2,2);//版本号//
    if(WSAStartup(wVerSion,&wsaData))
    {
        printf("WSAStart失败\n");
        return 0;
    }

    /*1、建立流型套接字*/
    SOCKET    hServer;
    hServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(hServer == INVALID_SOCKET)
    {
        printf("socket失败\n");
    }
    /*2、将套接字与远程主机连接*/
    sockaddr_in addrCustumer;

    addrCustumer.sin_family = AF_INET;
    addrCustumer.sin_port = htons(7758);
    //addrCustumer.sin_addr.s_addr = htonl(1921680109);
    //addrCustumer.sin_addr.s_addr = inet_addr("127.0.0.1");
    char Custumer[20];
    printf("请输入服务端IP:\n");
    memset(Custumer,0,20);
    scanf("%s",Custumer);
    addrCustumer.sin_addr.s_addr = inet_addr(Custumer);

    //connect连接到服务端
    int nLen = sizeof(addrCustumer);
    nRet = connect(hServer,(sockaddr*)&addrCustumer,nLen);
    if(nRet == -1)
    {
        printf("连接失败\n");
        GetLastError();
        closesocket(hServer);
        WSACleanup();
        return 0;
    }
    
    char szBuff[255];
    for(int i = 0;;i++)
    {
        nRet = send(hServer,"Hi",strlen("Hi"),0);
        if(nRet == SOCKET_ERROR)
        {
            printf("send出错\n");
            closesocket(hServer);
        //    closesocket(hClient);
            WSACleanup();
            return 0;
        }
        printf("信息已发送\n");
        
        memset(szBuff,0,255);

        nRet = recv(hServer,szBuff,255,0);    
        
        if(nRet == SOCKET_ERROR)
        {
            printf("recv\n");
            closesocket(hServer);
            WSACleanup();
            return 0;
        }
            
        printf("接受消息:");
        printf("%s\n",szBuff);
        if(i == 100)
        {
            nRet = send(hServer,"Close",strlen("Close"),0);

        }
    


    }
    


    //关闭//
    closesocket(hServer);
    //    closesocket(hClient);
        WSACleanup();
    return 0;
}

 

 

九、代码实验

1、我根据教程写了服务端,自己去写了客户端

 

我以虚拟机下的winxp作为服务端,以物理机为客户端,向同一局域网下的winxp发送信息

 

2、服务端

 

客户端:

 

我猜测,类似飞鸽或飞秋的局域网通信技术可能类似于这种方法

 

posted @ 2021-12-25 23:19  TLSN  阅读(491)  评论(0)    收藏  举报