Windows网络编程之Socket通信

一、Windows编程简介

1、Winsock简介

Winsock是Windows下网络编程的标准接口,它允许两个或多个应用程序在相同机器上,或者通过网络相互交流。Winsock库有两个版本,Winsock1和Winsock2,需要在程序中包含头文件winsock2.h,它包含了绝大部分socket函数和相关结构类型的声明和定义。同时要添加的还有到WS2_32.lib库的链接。包含必要的头文件,设置好链接环境之后,就可开始coding了。Winsock封装了TCP/IP协议,我们可以通过调用Winsock的接口函数来调用TCP/IP的各种功能。

2、Winsock编程流程

Winsock编程分为服务器端和客户端两部分,TCP服务器端程序创建过程:服务器端创建监听套接字,并为它关联一个本地地址(指定端口号和IP地址),然后进入监听状态准备接受客户端的连接请求。客户端程序创建过程:客户端创建套接字后即可调用connect函数去试图连接服务器监听套接字。当服务器端的accept函数返回后,connect函数也返回。此时客户端使用socket函数创建的套接字,服务器端使用accept函数创建的套接字,双方就可以通信了。

二、socket通信程序代码实现

开发环境:VC++ 6.0 、Win 7系统
ps:将服务器端工程和客户端工程添加到同一个工作区

1、服务器端:

/*
服务器端
*/

#include<stdio.h>
//WinSock是一个网络编程接口,是对TCP/IP协议的一种封装
#include<Winsock2.h>
#pragma comment(lib,"ws2_32")//链接一个ws2_32.lib的库文件


DWORD WINAPI RecvThread(void *Param){
   SOCKET socketConnection = *((SOCKET *)Param);    //设置socket
   char recvBuf[50];    
   while(1){//接收并显示消息
    recv(socketConnection,recvBuf,50,0);
    printf("%s\n",recvBuf);
   }
   return 0;
}

void main(){
    /*
    1.任何基于WinSock的编程首先必须初始化 WinSock DLL库
        ps:所有变量的定义要放到函数最前面,否则会报错:syntax error : missing ';' before 'type'
    */
    WORD wVersionRequested;//使用WinSock的版本为:wVersionRequested
    WSADATA wsaData;
    int err;
    SOCKET socketServer;
    SOCKADDR_IN addrSrv;
    SOCKADDR_IN addrClient;
    int len;
    SOCKET socketConnection;
    char sendBuf[50];



    wVersionRequested = MAKEWORD(1,1);
    err = WSAStartup(wVersionRequested , &wsaData);
    if(err!=0){
        return;
    }
    if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1 ){
        WSACleanup();
        return;
    }

    /*
    2.创建一个套接字。WinSock通信的所有的数据传输都是通过套接字来完成的
    */
    socketServer = socket(AF_INET,SOCK_STREAM,0);//创建套接字
    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
    addrSrv.sin_family=AF_INET;//将sin_family字段设置为AF_INET,代表WinSock使用的是IP地址族
    addrSrv.sin_port=htons(6000);//端口号
    /*
    3.通过绑定函数bind来实现套接字和需要进行通讯的地址建立联系
    */
    bind(socketServer,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//绑定套接字到一个IP地址和一个端口上
    listen(socketServer,5);//将套接字设置为监听模式等待连接请求
    len = sizeof(SOCKADDR);
    //请求到来后,接收连接请求,返回一个新的对应于此次连接的套接字
    socketConnection = accept(socketServer,(SOCKADDR*)&addrClient,&len);
    //创建一个子线程调用RecvThread函数,专门用来接收消息
    CreateThread(NULL,0,RecvThread,&socketConnection,0,NULL);
    //发送消息就在主线程当中实现
    while(1){
        printf("输入要发送给客户端的消息:\n");
        scanf("%s",sendBuf);    //从键盘输入要发送给客户端的消息,存入数组当中
        send(socketConnection,sendBuf,strlen(sendBuf)+1,0); //发送消息
    }
}

2、客户端:

/*
客户端程序
*/
#include<stdio.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32")//链接一个ws2_32.lib的库文件

//接收消息
DWORD WINAPI RecvThread(void *Param){
   SOCKET socketClient = *((SOCKET *)Param);    //设置socket
   char recvBuf[50];
   while(1){//接收并显示消息
    recv(socketClient,recvBuf,50,0);
    printf("%s\n",recvBuf);
   }
   return 0;
}

void main(){
    WORD wVersionRequested;
    WSADATA wsaData;
    SOCKET socketClient;//所有变量的定义要放到函数最前面,否则会报错:syntax error : missing ';' before 'type'
    SOCKADDR_IN addrSrv;
    //char recvBuf[50];//字符型数组,用来接收消息
    char sendBuf[50];//字符型数组,用来发送消息
    int err;

    wVersionRequested = MAKEWORD(1,1);
    err = WSAStartup(wVersionRequested,&wsaData);
    if(err!=0){
        return;
    }
    if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1 ){
        WSACleanup();//关闭加载的套接字库
        return;
    }
    socketClient = socket(AF_INET,SOCK_STREAM,0);
    addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//IP地址
    addrSrv.sin_family=AF_INET;//将sin_family字段设置为AF_INET,代表WinSock使用的是IP地址族
    addrSrv.sin_port=htons(6000);//端口号
    connect(socketClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//向服务器端发出连接请求
    //创建子线程调用RecvThread函数,专门用来接收服务器端发过来的消息
    CreateThread(NULL,0,RecvThread,&socketClient,0,NULL);
    //发送消息在主线程当中实现
    while(1){
        printf("请输入要发给服务器端的消息:\n");
        scanf("%s",sendBuf);
        send(socketClient,sendBuf,strlen(sendBuf)+1,0);//发送消息
    //  closesocket(socketClient);
    //  WSACleanup();//关闭加载的套接字库
    }

}

3、运行效果:

三、遇到的问题及解决办法

问题1:服务器端和客户端程序都不能连续发送消息。

原因:将接收消息和发送消息都放到主线程当中来操作,因为这两个操作需要一直运行,而主线程在同一时刻只能执行其中一个,所以势必会导致接收或者发送消息的其中一个阻塞,这就出现了服务器端和客户端程序都不能发送消息的问题。
解决办法:将客户端和服务器端的接收消息部分的代码都放到子线程当中去执行,而主线程就专心发送消息,这样就能实现客户端和服务器端都能连续的、即时的向对方发送消息。

问题2:增加子线程来接收消息后,如何保证接收和发送消息是处于同一次连接中。

解决办法:提前做好服务器和客户端的连接工作,返回一个套接字,然后在创建接收消息子线程的时候,将该套接字的引用传给CreateThread函数的第四个参数。这样就能保证收发消息是通过同一个套接字来处理的。

四、总结

1、CreateThread函数

//函数原型:
CreateThread(
_In_opt_LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_SIZE_T dwStackSize,
_In_LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt___drv_aliasesMemLPVOID lpParameter,
_In_DWORD dwCreationFlags,
_Out_opt_LPDWORD lpThreadId
);


//参数说明
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。
//也可以直接调用void类型
//但lpStartAddress要这样通过LPTHREAD_START_ROUTINE转换如: (LPTHREAD_START_ROUTINE)MyVoid
//然后在线程声明为:
void MyVoid()
{
return;
}
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
(20:表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新线程的id。
返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL。
函数说明:
创建一个线程。
语法:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值