C语言socket编程详解(附带实例)
套接字是网络通信的基本构件,最初是加利福尼亚大学伯克利分校为 UNIX 开发的网络通信编程接口。为了在 Windows 操作系统上使用套接字,20 世纪 90 年代初,微软和第三方厂商共同制定了一套标准,即 Windows Socket 规范,简称 WinSock。
所谓套接字,实际上是一个指向传输提供者的句柄。在 WinSock 中,就是通过操作该句柄来实现网络通信和管理的。
根据性质和作用的不同,套接字可以分为原始套接字、流式套接字和数据包套接字 3 种:
基于 TCP,面向连接的 socket 编程的服务器端程序流程如下:
基于 TCP,面向连接的 socket 编程的客户端程序流程如下:
在服务器端,当调用 accept() 函数时,程序就会进行等待,直到有客户端调用 connect 函数发送连接请求,然后服务器接受该请求,这样服务器端与客户端就建立了连接,可以开始通信了。
注意,在服务器端要建立套接字绑定到指定的主机 IP 和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的IP地址和端口号的信息。对于服务器端来说,一旦建立连接,实际上它已经保存了客户端的IP地址和端口号的信息,因此可以利用返回的套接字进行与客户端的通信。
对于基于 UDP,面向无连接的套接字编程来说,服务器端和客户端的概念不是特别的严格。可以把服务器称为接收端,客户端就是发送数据的发送端。
基于 UDP,面向无连接的 socket 编程的接收端程序流程如下:
基于 UDP,面向无连接的 socket 编程的发送端程序流程如下:
注意,基于 UDP 的套接字编程中,仍然需要使用 bind 进行绑定。虽然面向无连接的 socket 编程无须建立连接,但为了完成通信,首先应启动接收端,等待接收发送端发来的数据,这样接收端就必须告诉它自己的地址和端口。因此,必须调用 bind 函数将套接字绑定到一个本地地址和端口上。
基于 UDP 的套接字编程时,利用的是 sendto() 和 recvfrom() 两个函数实现数据的发送和接收;基于 TCP 的套接字编程时,发送数据使用的是 send() 函数,接收数据使用的是 recv() 函数。
例如,使用 WSAStartup 初始化套接字,版本号为 2.2:
例如,使用 socket() 函数创建一个套接字 socket_server:
在创建了套接字后,应该将该套接字绑定到本地的某个地址和端口上,这时就需要使用 bind() 函数了。例如,使用 bind() 函数绑定一个套接字:
例如,使用 listen() 函数设置套接字为监听状态:
例如,使用 accept() 函数接受客户端的连接请求,代码如下:
例如,使用 closesocket() 函数关闭套接字,释放客户端的套接字资源,代码如下:
例如,使用 connect() 函数与一个套接字建立连接,代码如下:
例如,使用 htons() 函数对一个无符号短整型数据进行转换,代码如下:
其使用方式与 htons() 函数相似,不过是将一个 32 位数值转换为 TCP/IP 网络字节顺序。
例如,使用 inet_addr() 函数将一个以点分十进制格式表示的 IP 地址 192.168.1.43 转换为 32 位的无符号长整型数据,代码如下:
例如,使用 recv() 函数接收数据,代码如下:
例如,使用 send() 函数发送数据,代码如下:
根据有关 TCP 套接字 socket 编程中的客户端设计过程,编写下面的代码:
先运行第一个程序,然后运行第二个程序。首先在客户端输入数据,按 Enter 键后,即可以在服务器端看到输入的信息。客户端输入完后,服务器端就可以对其进行回复。在服务器端输入数据并按 Enter 键,可发送消息到客户端。
客户端程序运行效果为:
服务器端程序运行效果为:
注意,要实现网络通信,一定先运行服务器端,再运行客户端。注意,在客户端输入 IP 地址时,计划与哪台计算机通信,就输入哪台计算机的 IP 地址。
所谓套接字,实际上是一个指向传输提供者的句柄。在 WinSock 中,就是通过操作该句柄来实现网络通信和管理的。
根据性质和作用的不同,套接字可以分为原始套接字、流式套接字和数据包套接字 3 种:
- 原始套接字:是在 WinSock 2 规范中提出的,它能够使程序开发人员对底层的网络传输机制进行控制,在原始套接字下接收的数据中包含 IP 头;
- 流式套接字:提供双向、有序、可靠的数据传输服务。该类型套接字在通信前需要双方建立连接,大家熟悉的 TCP 协议采用的就是流式套接字;
- 数据包套接字:提供双向的数据流,但不能保证数据传输的可靠性、有序性和无重复性。UDP 协议采用的就是数据包套接字。
TCP套接字的socket编程
TCP 是面向连接的可靠的传输协议。利用 TCP 协议进行通信时,首先要建立通信双方的连接。一旦连接建立完成,就可以进行通信。TCP 提供了数据确认和数据重传的机制,保证了发送的数据一定能到达通信的对方。基于 TCP,面向连接的 socket 编程的服务器端程序流程如下:
- 创建套接字 socket;
- 将创建的套接字绑定(bind)到本地的地址和端口上;
- 设置套接字的状态为监听状态(listen),准备接受客户端的连接请求;
- 接受请求(accept),同时返回得到一个用于连接的新套接字;
- 使用这个新套接字进行通信(通信函数使用 send/recv);
- 通信完毕,释放套接字资源(closesocket)。
基于 TCP,面向连接的 socket 编程的客户端程序流程如下:
- 创建套接字 socket;
- 向服务器发出连接请求(connect);
- 连接后,与服务器进行通信操作(send/recv);
- 释放套接字资源(closesocket)。
在服务器端,当调用 accept() 函数时,程序就会进行等待,直到有客户端调用 connect 函数发送连接请求,然后服务器接受该请求,这样服务器端与客户端就建立了连接,可以开始通信了。
注意,在服务器端要建立套接字绑定到指定的主机 IP 和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的IP地址和端口号的信息。对于服务器端来说,一旦建立连接,实际上它已经保存了客户端的IP地址和端口号的信息,因此可以利用返回的套接字进行与客户端的通信。
UDP套接字的socket编程
UDP 是无连接的不可靠的传输协议。采用 UDP 进行通信时,不需要建立连接,可以直接向一个 IP 地址发送数据,但是不能保证对方能收到。对于基于 UDP,面向无连接的套接字编程来说,服务器端和客户端的概念不是特别的严格。可以把服务器称为接收端,客户端就是发送数据的发送端。
基于 UDP,面向无连接的 socket 编程的接收端程序流程如下:
- 创建套接字 socket;
- 将套接字绑定(bind)到一个本地地址和端口上;
- 等待接收数据(recvfrom);
- 释放套接字资源(closesocket)。
基于 UDP,面向无连接的 socket 编程的发送端程序流程如下:
- 创建套接字 socket;
- 向服务器发送数据(sendto);
- 释放套接字资源(closesocket)。
注意,基于 UDP 的套接字编程中,仍然需要使用 bind 进行绑定。虽然面向无连接的 socket 编程无须建立连接,但为了完成通信,首先应启动接收端,等待接收发送端发来的数据,这样接收端就必须告诉它自己的地址和端口。因此,必须调用 bind 函数将套接字绑定到一个本地地址和端口上。
基于 UDP 的套接字编程时,利用的是 sendto() 和 recvfrom() 两个函数实现数据的发送和接收;基于 TCP 的套接字编程时,发送数据使用的是 send() 函数,接收数据使用的是 recv() 函数。
套接字常用函数
前面介绍了使用套接字编写程序的流程,接下来介绍使用套接字编程时用到的函数。1) WSAStartup()函数
WSAStartup() 函数的功能是初始化套接字库。其原型如下:int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);注意,WSAStartup() 函数用于初始化 Ws2_32.dll 动态链接库。在使用其他套接字函数之前,必须先初始化 Ws2_32.dll 动态链接库。
- wVersionRequested:表示调用者使用的 WinSock 版本,高字节记录修订版本,低字节记录主版本。例如,如果 WinSock 的版本为 2.1,则高字节记录 1,低字节记录 2。
- lpWSAData:WSADATA 结构指针,详细记录了 Windows 套接字的相关信息。其定义如下:
typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; } WSADATA, FAR * LPWSADATA;
- wVersion:表示调用者使用的 WS2_32.dll 动态库的版本号;
- wHighVersion:表示 WS2_32.dll 支持的最高版本,通常与 wVersion 相同;
- szDescription:表示套接字的描述信息,通常没有实际意义;
- szSystemStatus:表示系统的配置或状态信息,通常没有实际意义;
- iMaxSockets:表示最多可以打开多少个套接字。在 WinSock 2 及以后的版本中,该成员将被忽略;
- iMaxUdpDg:表示数据报的最大长度。在 WinSock 2 及以后的版本中,该成员将被忽略;
- lpVendorInfo:表示套接字的厂商信息。在 WinSock 2 及以后的版本中,该成员将被忽略。
例如,使用 WSAStartup 初始化套接字,版本号为 2.2:
WORD wVersionRequested; /* WORD(字),类型为 unsigned short */ WSADATA wsaData; /* 库版本信息结构 */ /* 定义版本类型,将两个字节组合成一个字,前面是低字节,后面是高字节 */ wVersionRequested = MAKEWORD(2, 2); /* 表示版本号 */ /* 加载套接字库,初始化 Ws2_32.dll 动态链接库 */ WSAStartup(wVersionRequested, &wsaData);从上面的代码中可以看出,MAKEWORD 宏的作用是:根据给定的两个无符号字节,创建一个 16 位的无符号整型,将创建的值赋予 wVersionRequested 变量,表示套接字的版本号。
2) socket函数
socket() 函数的功能是创建一个套接字。其原型如下:SOCKET socket(int af,int type, int protocol);
- af:表示一个地址家族,通常为 AF_INET;
- type:表示套接字类型。如果为 SOCK_STREAM,表示创建面向连接的流式套接字;如果为 SOCK_DGRAM,表示创建面向无连接的数据报套接字;如果为 SOCK_RAW,表示创建原始套接字。对于这些值,用户可以在 winsock2.h 头文件中找到;
- protocol:表示套接字采用的协议,如果用户不指定,可以设置为 0;
- 返回值:创建的套接字句柄。
例如,使用 socket() 函数创建一个套接字 socket_server:
/*创建服务器套接字*/ /*AF_INET表示指定地址族,SOCK_STREAM表示流式套接字TCP,特定的地址家族相关的协议*/ socket_server=socket(AF_INET,SOCK_STREAM,0);在上面代码中,如果 socket() 函数调用成功,就会返回一个新的 SOCKET 数据类型的套接字描述符。使用定义好的套接字 socket_server 进行保存。
3) bind()函数
bind() 函数的功能是将套接字绑定到指定的端口和地址上。其原型如下:int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);
- s:套接字标识;
- name:sockaddr 结构指针,包含了要结合的地址和端口号;
- namelen:表示 name 缓冲区的长度;
- 返回值:如果函数执行成功,则返回值为 0,否则为 SOCKET_ERROR。
在创建了套接字后,应该将该套接字绑定到本地的某个地址和端口上,这时就需要使用 bind() 函数了。例如,使用 bind() 函数绑定一个套接字:
SOCKADDR_IN Server_add; /* 服务器地址信息结构 */ Server_add.sin_family = AF_INET; /* 地址族,必须是 AF_INET,注意只有它不是网络字节顺序 */ Server_add.sin_addr.S_un.S_addr = htonl(INADDR_ANY); /* 主机地址 */ Server_add.sin_port = htons(5000); /* 端口号 */ bind(socket_server, (SOCKADDR*)&Server_add, sizeof(SOCKADDR)); /* 使用 bind 函数进行绑定 */
4) listen()函数
listen() 函数的功能是将套接字设置为监听模式。对于流式套接字,必须处于监听模式才能够接收客户端套接字的连接。该函数的原型如下:int listen(SOCKET s, int backlog);
- s:套接字标识;
- backlog:表示等待连接的最大队列长度。例如,如果 backlog 被设置为 2,此时有 3 个客户端同时发出连接请求,那么前两个客户端连接会放置在等待队列中,第 3 个客户端会得到错误信息。
例如,使用 listen() 函数设置套接字为监听状态:
listen(socket_server,5);上述代码中,设置套接字为监听状态,为连接做准备,最大等待的数目为 5。
5) accept()函数
accept() 函数的功能是接受客户端的连接。在流式套接字中,只有当套接字处于监听状态时,才能接受客户端的连接。该函数的原型如下:SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
- s:套接字标识,它应处于监听状态;
- addr:sockaddr_in 结构指针,包含一组客户端的端口号、IP 地址等信息;
- addrlen:用于接收参数 addr 的长度;
- 返回值:一个新的套接字,对应于已经接受的客户端连接。对于该客户端的所有后续操作,都应使用这个新的套接字。
例如,使用 accept() 函数接受客户端的连接请求,代码如下:
/*接受客户端的发送请求,等待客户端发送connect请求*/ socket_receive=accept(socket_server,(SOCKADDR*)&Client_add,&Length);其中,socket_receive 用于保存接受请求后返回的新的套接字,socket_server 为绑定在地址和端口上的套接字,而 Client_add 是有关客户端的 IP 地址和端口的信息结构,最后的 Length 是 Client_add 的大小(可以使用 sizeof() 函数取得,然后用 Length 变量保存)。
6) closesocket()函数
closesocket() 函数的功能是关闭套接字。其原型如下:int closesocket(SOCKET s);其中,s 是套接字标识。如果参数 s 设置了 SO_DONTLINGER 选项,则调用该函数后会立即返回。此时如果有数据尚未传送完毕,则会继续传递数据,然后再关闭套接字。
例如,使用 closesocket() 函数关闭套接字,释放客户端的套接字资源,代码如下:
closesocket(socket_receive); /* 释放客户端的套接字资源 */在代码中,socket_receive 是一个套接字,当不使用时就可以利用 closesocket() 函数将其资源释放。
7) connect函数
connect() 函数的功能是发送一个连接请求。其原型如下:int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
- s:套接字标识;
- name:表示套接字 s 要连接的主机地址和端口号;
- namelen:表示 name 缓冲区的长度。
- 返回值:如果函数执行成功,则返回值为 0,否则为 SOCKET_ERROR。用户可以通过 WSAGETLASTERROR 得到其错误描述。
例如,使用 connect() 函数与一个套接字建立连接,代码如下:
connect(socket_send,(SOCKADDR*)&Server_add,sizeof(SOCKADDR));在上面代码中,socket_send 表示要与服务器建立连接的套接字,而 Server_add 是要连接的服务器地址信息。
8) htons() 函数
htons() 函数的功能是将一个 16 位的无符号短整型数据从主机排列方式转换为网络排列方式。其原型如下:u_short htons(u_short hostshort);
- hostshort:主机排列方式的 16 位无符号短整型数据;
- 返回值:网络排列方式的 16 位无符号短整型数据。
例如,使用 htons() 函数对一个无符号短整型数据进行转换,代码如下:
Server_add.sin_port=htons(5000);在上面代码中,Sever_add 是有关主机地址和端口的结构,其中 sin_port 表示的是端口号。因为端口号要使用网络排列方式,所以使用 htons() 函数进行转换,设定新的端口号。
9) htonl()函数
htonl() 函数的功能是将一个无符号长整型数据从主机排列方式转换为网络排列方式。其原型如下:u_long htonl(u_long hostlong);
- hostlong:主机排列方式的 32 位无符号长整型数据;
- 返回值:网络排列方式的 32 位无符号长整型数据。
其使用方式与 htons() 函数相似,不过是将一个 32 位数值转换为 TCP/IP 网络字节顺序。
10) inet_addr函数
inet_addr() 函数的功能是将一个由点分十进制表示的 IP 地址字符串转换为 32 位的无符号长整型数据。其原型如下:unsigned long inet_addr(const char FAR * cp);
- cp:表示 IP 地址的字符串;
- 返回值:32 位无符号长整型数据。
例如,使用 inet_addr() 函数将一个以点分十进制格式表示的 IP 地址 192.168.1.43 转换为 32 位的无符号长整型数据,代码如下:
Server_add.sin_addr.S_un.S_addr = inet_addr("192.168.1.43");
11) recv() 函数
recv() 函数的功能是从面向连接的套接字中接收数据。其原型如下:int recv(SOCKET s,char FAR* buf,int len,int flags);
- s:套接字标识;
- buf:表示接收数据的缓冲区;
- len:表示 buf 的长度;
- flags:表示函数的调用方式。如果为 MSG_PEEK,表示查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走;如果为 MSG_OOB,表示处理 Out-Of-Band 数据,也就是外带数据。
例如,使用 recv() 函数接收数据,代码如下:
recv(socket_send,Receivebuf,100,0);其中,socket_send 是用于连接的套接字,Receivebuf 是用来接收保存数据的空间,100 是该空间的大小。
12) send()函数
send() 函数用于在面向连接方式的套接字间发送数据。其原型如下:int send(SOCKET s,const char FAR * buf, int len,int flags);
- s:套接字标识;
- buf:表示存放要发送数据的缓冲区;
- len:表示缓冲区长度;
- flags:表示函数的调用方式。
例如,使用 send() 函数发送数据,代码如下:
send(socket_receive,Sendbuf,100,0);在上面代码中,socket_receive 用于连接的套接字,而 Sendbuf 保存要发送的数据,100 为该数据的大小。
13) recvfrom()函数
recvfrom() 函数用于接收一个数据报信息并保存源地址。其原型如下:int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen);
- s:表示准备接收数据的套接字;
- buf:指向缓冲区的指针,用来接收数据;
- len:表示缓冲区的长度;
- flags:表示函数的调用方式;
- from:一个指向地址结构的指针,用来接收发送数据方的地址信息;
- fromlen:表示缓冲区的长度。
14) sendto()函数
sendto() 函数用于向一个特定的目的方发送数据。其原型如下:int sendto(SOCKET s,const char FAR * buf,int len,int flags,const struct sockaddr FAR * to,int tolen);
- s:套接字标识符(可能已经建立连接);
- buf:指向缓冲区的指针,该缓冲区包含将要发送的数据;
- len:表示缓冲区的长度;
- flags:表示函数的调用方式;
- to:指定目标套接字的地址;
- tolen:表示缓冲区的长度。
15) WSACleanup()函数
WSACleanup() 函数用于释放为 Ws2_32.dll 动态链接库初始化时分配的资源。其原型如下:int WSACleanup(void);例如,使用该函数可关闭动态链接库,代码如下:
WSACleanup(); /*关闭动态链接库*/
基于TCP的网络聊天程序
接下来将编写一个基于 TCP 网络通信的聊天程序,希望通过本案例,读者可对前面学习的内容有一个更深的理解。#include<stdio.h> #include<winsock.h> /* 包含 winsock 头文件 */ int main() { /*--------------------------定义变量---------------------------*/ char Sendbuf[100]; /* 发送数据的缓冲区 */ char Receivebuf[100]; /* 接收数据的缓冲区 */ int SendLen; /* 发送数据的长度 */ int ReceiveLen; /* 接收数据的长度 */ int Length; /* SOCKADDR 的大小 */ SOCKET socket_server; /* 定义服务器套接字 */ SOCKET socket_receive; /* 定义连接套接字 */ SOCKADDR_IN Server_add; /* 服务器地址信息结构 */ SOCKADDR_IN Client_add; /* 客户端地址信息结构 */ WORD wVersionRequested; /* 字(word):unsigned short */ WSADATA wsaData; /* 库版本信息结构 */ int error; /*--------------------------初始化套接字---------------------------*/ /* 定义版本类型,将两个字节组合成一个字,前面是低字节,后面是高字节 */ wVersionRequested = MAKEWORD(2, 2); /* 表示版本号 */ /* 加载套接字库,初始化 Ws2_32.dll 动态链接库 */ error = WSAStartup(wVersionRequested, &wsaData); if(error!=0) { printf("加载套接字失败!"); return 0; /* 程序结束 */ } /* 判断请求加载的版本号是否符合要求 */ if((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) ) { WSACleanup(); /* 不符合,关闭套接字库 */ return 0; /* 程序结束 */ } /*--------------------------设置连接地址---------------------------*/ /*-------------------------------------------*/ Server_add.sin_family = AF_INET; /* 地址族,必须是 AF_INET。注意,只有它不是网络字节顺序 */ Server_add.sin_addr.S_un.S_addr = htonl(INADDR_ANY); /* 主机地址 */ Server_add.sin_port = htons(5000); /* 端口号 */ /*--------------------------创建套接字---------------------------*/ /* AF_INET 表示指定地址族,SOCK_STREAM 表示该套接字 TCP,特定的地址族相关协议 */ socket_server = socket(AF_INET, SOCK_STREAM, 0); /*--------------------------绑定套接字到本地的某个地址和端口上------------*/ /*-------------------------------------------*/ /* socket_server 为套接字,(SOCKADDR*)&Server_add 为服务器地址 */ if(bind(socket_server, (SOCKADDR*)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR) { printf("绑定失败\n"); } /*--------------------------设置套接字为监听状态---------------------------*/ /* 监听状态,为连接做准备,最大等待数目为 5 */ if(listen(socket_server, 5) < 0) { printf("监听失败\n"); } /*--------------------------接受连接-------------------------- */ Length = sizeof(SOCKADDR); /* 接受客户端的发送请求,等待客户端发送 connect 请求 */ socket_receive = accept(socket_server, (SOCKADDR*)&Client_add, &Length); if(socket_receive == SOCKET_ERROR) { printf("接受连接失败\n"); } /*--------------------------进行聊天-------------------------- */ /*------------------------------------------ */ while(1) /* 无限循环 */ { /*--------------------------接收数据-------------------------- */ ReceiveLen = recv(socket_receive, Receivebuf, 100, 0); if(ReceiveLen < 0) { printf("接收失败\n"); printf("程序退出\n"); break; } else { printf("client say: %s\n", Receivebuf); } /*--------------------------发送数据-------------------------- */ printf("please enter message:"); scanf("%s", Sendbuf); SendLen = send(socket_receive, Sendbuf, 100, 0); if(SendLen < 0) { printf("发送失败\n"); } } /*--------------------------释放套接字,关闭动态库-------------------------- */ closesocket(socket_receive); /* 释放客户端的套接字资源 */ closesocket(socket_server); /* 释放服务器的套接字资源 */ WSACleanup(); /* 关闭动态链接库 */ return 0; }运行程序之前,要先添加相应的库文件 ws2_32.lib。以上就是网络聊天程序服务器端的代码。整个程序流程按照以下顺序编写:
- 创建套接字;
- 绑定套接字到本地的地址和端口上;
- 设置套接字为监听状态;
- 接受请求连接的请求;
- 进行通信;
- 通信完毕,释放套接字资源。
根据有关 TCP 套接字 socket 编程中的客户端设计过程,编写下面的代码:
#include<stdio.h> #include<winsock.h> /* 包含 winsock 头文件 */ int main() { /*--------------------------定义变量---------------------------*/ char Sendbuf[100]; /* 发送数据的缓冲区 */ char Receivebuf[100]; /* 接收数据的缓冲区 */ int SendLen; /* 发送数据的长度 */ int ReceiveLen; /* 接收数据的长度 */ SOCKET socket_send; /* 定义套接字 */ SOCKADDR_IN Server_add; /* 服务器地址信息结构 */ WORD wVersionRequested; /* 字(word):unsigned short */ WSADATA wsaData; /* 库版本信息结构 */ int error; /*--------------------------初始化套接字---------------------------*/ /* 加载套接字库,初始化 Ws2_32.dll 动态链接库 */ error = WSAStartup(wVersionRequested, &wsaData); if(error!=0) { printf("加载套接字失败!"); return 0; /* 程序结束 */ } /* 判断请求加载的版本号是否符合要求 */ if((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) ) { WSACleanup(); /* 不符合,关闭套接字库 */ return 0; /* 程序结束 */ } /*--------------------------设置服务器地址---------------------------*/ Server_add.sin_family = AF_INET; /* 地址族,必须是 AF_INET。注意,只有它不是网络字节顺序 */ Server_add.sin_addr.S_un.S_addr = inet_addr("192.168.1.43"); /* 服务器地址,将一个点分十进制表示为 IP 地址,inet_ntoa 是将地址转换成字符串 */ Server_add.sin_port = htons(5000); /* 端口号 */ /*--------------------------进行服务器连接---------------------------*/ /* 客户端创建套接字,但是不需要绑定,只需要和服务器建立起连接即可 */ /* socket_send 表示的是套接字,Server_add 是服务器的地址结构 */ socket_send = socket(AF_INET, SOCK_STREAM, 0); /*--------------------------创建用于连接的套接字---------------------------*/ /* AF_INET 表示指定地址族,SOCK_STREAM 表示流式套接字 TCP,特定的地址族相关协议 */ if(connect(socket_send, (SOCKADDR*)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR) { printf("连接失败\n"); } /*--------------------------进行聊天-------------------------- */ while(1) /* 无限循环 */ { /*--------------------------发送数据过程-------------------------- */ printf("please enter message:"); scanf("%s", Sendbuf); SendLen = send(socket_send, Sendbuf, 100, 0); /* 发送数据 */ if(SendLen < 0) { printf("发送失败\n"); } /*--------------------------接收数据过程-------------------------- */ ReceiveLen = recv(socket_send, Receivebuf, 100, 0); /* 接收数据 */ if(ReceiveLen < 0) { printf("接收失败\n"); printf("程序退出\n"); break; /* 跳出循环 */ } else { printf("Server say: %s\n", Receivebuf); } } /*--------------------------释放套接字,关闭动态库-------------------------- */ closesocket(socket_send); /* 释放套接字资源 */ WSACleanup(); /* 关闭动态链接库 */ return 0; }以上就是网络聊天程序客户端的代码。整个程序流程按照以下顺序编写:
- 创建套接字;
- 发出连接请求;
- 请求连接后进行通信操作;
- 释放套接字资源。
先运行第一个程序,然后运行第二个程序。首先在客户端输入数据,按 Enter 键后,即可以在服务器端看到输入的信息。客户端输入完后,服务器端就可以对其进行回复。在服务器端输入数据并按 Enter 键,可发送消息到客户端。
客户端程序运行效果为:
please enter message:Hello!
Server say: Hello~
please enter message:
服务器端程序运行效果为:
client say: Hello!
please enter message:Hello~
注意,要实现网络通信,一定先运行服务器端,再运行客户端。注意,在客户端输入 IP 地址时,计划与哪台计算机通信,就输入哪台计算机的 IP 地址。