基于 TCP 的 socket 编程
/*
服务器端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.将我们创建的套接字,绑定到本机地址的某一端口上 bind
4.为套接字设置监听模式,准备客户请求 listen
5.等待客户请求到来。当请求到来,将接受连接请求,并返回一个新的对应于此次连接的套接字 accept
6.用新返回的套接字和客户端进行通信 send / recv
7.在通信结束后,关闭套接字 closesocket
客户端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.向服务器发出请求连接 connect
4.和服务器进行通信 send / recv
5.在通信结束后,关闭套接字 closesocket
*/
服务器端代码:
- #include <Winsock2.h>
- #include <stdio.h>
- #pragma comment(lib, "Ws2_32.lib")
- void main()
- {
- // 加载套接字库,并进行套接字的版本协商
- WORD wVersionRequested; // 指定将要加载的 winsock 库版本
- WSADATA wsaData; // 用于存储加载的 winsock 库版本信息
- int result; // 用于检测 WSAStartup 函数运行结果
- wVersionRequested = MAKEWORD(1, 1); // 设定版本
- result = WSAStartup(wVersionRequested, &wsaData);
- // 函数 WSAStartup 调用成功返回 0
- // 出错处理
- if (result != 0)
- {
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- WSACleanup();
- return;
- }
- // 创建套接字
- SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
- // 绑定套接字
- SOCKADDR_IN addrInfo; // 存储本地主机地址信息
- addrInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本地主机地址
- addrInfo.sin_port = htons(6000); // 端口号
- addrInfo.sin_family = AF_INET; // 地址族
- bind(sock, (SOCKADDR *)&addrInfo, sizeof(SOCKADDR));
- // 设置套接字监听模式
- listen(sock, 5);
- SOCKADDR_IN addrInfoClient; // 存储客户端地址信息
- int len = sizeof(SOCKADDR);
- while (true)
- {
- // 等待客户请求到来,并返回用于通信的套接字
- SOCKET sockConnect = accept(sock, (SOCKADDR *)&addrInfoClient, &len);
- // 下面通过刚建立的套接字,来进行通信
- // 发送数据
- char sendBuf[100];
- sprintf(sendBuf, "这是服务器端,主机地址:%s", inet_ntoa(addrInfo.sin_addr));
- send(sockConnect, sendBuf, strlen(sendBuf), 0);
- // 接收数据
- char recvBuf[100];
- recv(sockConnect, recvBuf, strlen(recvBuf), 0);
- // 打印接收的数据
- printf("%s\n", recvBuf);
- closesocket(sockConnect);
- }
- }
客户端代码:
- #include <Winsock2.h>
- #include <stdio.h>
- #pragma comment(lib,"Ws2_32.lib")
- void main()
- {
- // 加载套接字库,并进行套接字的版本协商
- WORD wVersionRequested; // 指定将要加载的 winsock 库版本
- WSADATA wsaData; // 用于存储加载的 winsock 库版本信息
- int result; // 用于检测 WSAStartup 函数运行结果
- wVersionRequested = MAKEWORD(1, 1); // 设定版本
- result = WSAStartup(wVersionRequested, &wsaData);
- // 函数 WSAStartup 调用成功返回 0
- // 出错处理
- if (result != 0)
- {
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- WSACleanup();
- return;
- }
- // 创建套接字
- SOCKET sockConnect = socket(AF_INET, SOCK_STREAM, 0);
- // 向服务器发出连接请求
- SOCKADDR_IN addrInfoServer; // 存储服务器端地址信息
- addrInfoServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- addrInfoServer.sin_port = htons(6000);
- addrInfoServer.sin_family = AF_INET;
- // 向服务器发出连接请求
- connect(sockConnect, (SOCKADDR *)&addrInfoServer, sizeof(SOCKADDR));
- // 接收数据
- char recvBuf[100];
- recv(sockConnect, recvBuf, sizeof(recvBuf), 0);
- printf("%s\n", recvBuf);
- // 发送数据
- char sendBuf[100] = "这是客户端\n";
- send(sockConnect, sendBuf, sizeof(sendBuf) + 1, 0);
- //关闭套接字
- closesocket(sockConnect);
- WSACleanup();
- system("pause");
- return;
- }
================================================
基于 UDP 无连接的 socket 编程
/*
服务端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.将创建的套接字绑定到一个本地地址和端口上 bind
4.等待接收数据。后与客户端实现实时交流 recvfrom / sendto
5.关闭套接字 closesocket
客户端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.向服务器发送数据.后与服务端实现实时交流 recvfrom / sendto
4.关闭套接字 closesocket
*/
服务器端代码:
- #include <Winsock2.h>
- #include <stdio.h>
- #pragma comment(lib, "Ws2_32.lib")
- void main()
- {
- // 加载套接字库,并进行套接字的版本协商
- WORD wVersionRequested; // 指定将要加载的 winsock 库版本
- WSADATA wsaData; // 用于存储加载的 wdnsock 库版本信息
- int result; // 用于检测 WSAStartup 函数运行结果
- wVersionRequested = MAKEWORD(1, 1); // 设定版本
- result = WSAStartup(wVersionRequested, &wsaData);
- // 函数 WSAStartup 调用成功返回 0
- // 出错处理
- if (result != 0)
- {
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- WSACleanup();
- return;
- }
- // 创建用于套接字
- SOCKET sockConnect = socket(AF_INET, SOCK_DGRAM, 0);
- // 绑定套接字
- SOCKADDR_IN addrInfo; // 存储本地主机地址信息
- addrInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本地主机地址
- addrInfo.sin_port = htons(6000); // 端口号
- addrInfo.sin_family = AF_INET; // 地址族
- bind(sockConnect, (SOCKADDR *)&addrInfo, sizeof(SOCKADDR));
- // 等待接收数据
- char recvBuf[100]; // 接收数据缓冲
- char sendBuf[100]; // 发送数据缓冲
- char tempBuf[200];
- SOCKADDR_IN addrInfoClient; // 存储客户端地址信息
- int len = sizeof(SOCKADDR);
- while (true)
- {
- recvfrom(sockConnect, recvBuf, strlen(recvBuf), 0, (SOCKADDR *)&addrInfoClient, &len);
- if ('q' == recvBuf[0])
- {
- sendto(sockConnect, "q", strlen("q") + 1, 0, (SOCKADDR *)&addrInfoClient, len);
- printf("聊天结束");
- break;
- }
- sprintf(tempBuf, "%s 说:%s", inet_ntoa(addrInfoClient.sin_addr), recvBuf);
- printf("%s\n", tempBuf);
- // 发送数据
- printf("我说:");
- gets(sendBuf);
- sendto(sockConnect, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrInfoClient, len);
- }
- // 关闭套接字
- closesocket(sockConnect);
- WSACleanup();
- }
客户端代码:
- #include <Winsock2.h>
- #include <stdio.h>
- #pragma comment(lib, "Ws2_32.lib")
- void main()
- {
- // 加载套接字库,并进行套接字的版本协商
- WORD wVersionRequested; // 指定将要加载的 winsock 库版本
- WSADATA wsaData; // 用于存储加载的 wdnsock 库版本信息
- int result; // 用于检测 WSAStartup 函数运行结果
- wVersionRequested = MAKEWORD(1, 1); // 设定版本
- result = WSAStartup(wVersionRequested, &wsaData);
- // 函数 WSAStartup 调用成功返回 0
- // 出错处理
- if (result != 0)
- {
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- WSACleanup();
- return;
- }
- // 创建套接字
- SOCKET sockConnect = socket(AF_INET, SOCK_DGRAM, 0);
- // 向服务器发送数据
- SOCKADDR_IN addrInfoServer; // 存储服务器地址信息
- addrInfoServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 指定服务器地址
- addrInfoServer.sin_port = htons(6000); // 端口号
- addrInfoServer.sin_family = AF_INET; // 地址族
- int len = sizeof(SOCKADDR);
- char recvBuf[100]; // 接收数据缓冲
- char sendBuf[100]; // 发送数据缓冲
- char tempBuf[200];
- while (true)
- {
- // 发送数据
- printf("我说:");
- gets(sendBuf);
- sendto(sockConnect, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrInfoServer, len);
- // 等待并接收数据
- recvfrom(sockConnect,recvBuf, strlen(recvBuf), 0, (SOCKADDR*)&addrInfoServer, &len);
- if ('q' == recvBuf[0])
- {
- sendto(sockConnect, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrInfoServer, len);
- printf("聊天结束");
- break;
- }
- sprintf(tempBuf, "%s 说:%s", inet_ntoa(addrInfoServer.sin_addr), recvBuf);
- printf("%s\n", tempBuf);
- }
- // 关闭套接字
- closesocket(sockConnect);
- WSACleanup();
- }
- vc网络编程常用类型解析:
- 1. SOCKET 类型
- SOCKET 是 socket 套接字类型,在 WINSOCK2.H 中有如下定义:
- typedef unsigned u_int;
- typedef u_int SOCKET;
- 可知套接字实际上就是一个无符号整形,它将被 Socket 环境管理和使用。
- 套接字将被创建、设置、用来发送和接收数据,最后会被关闭。
- 2.WORD 类型、MAKEWORD、LOBYTE、HIBYTE 宏
- WORD 类型是一个 16 位的无符号整型, 在 WTYPES.H 中被定义为:
- typedef unsigned short WORD;
- 其目的是提供两个字节的存储, 在 Socket 中这两个字节可以表示主版本号和副版本号。
- 使用 MAKEWORD 宏可以给一个 WORD 类型赋值。例如要表示主版本号 2, 副版本号 0,可以使用如下代码:
- WORD wVersionRequested;
- wVersionRequested = MAKEWORD(2, 0);
- 注意低位内存存储主版本号 2, 高位内存存储副版本号 0,其值为 0x0002。
- 使用宏 LOBYTE 可以读取 WORD 的低位字节, HIBYTE 可以读取高位字节。
- 3.WSADATA 类型和 LPWSADATA 类型
- WSADATA 类型是一个结构,描述了 Socket 库的一些相关信息,其结构定义如下:
- 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;
- typedef WSADATA FAR* LPWSADATA;
- 值得注意的是 wVersion 字段,存储了 Socket 的版本类型。LPWSADATA 是 WSADATA 的指针类型。
- 他们通过 Socket 的初始化函数 WSAStartup 读取出来。
- vc网络编程常用函数解析:
- 1. WSAStartup 函数
- 用于初始化 Socket 环境,函数原型:
- int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- 其返回值为整型,调用方式为 PASCAL (即标准类型,PASCAL 等于__stdcall),参数有两个,
- 第一个参数为 WORD 类型,指明了 Socket 的版本号,第二个参数为 LPWSADATA,指向一个用于存储 Socket 库信息的WSAStartup结构。
- 返回值:
- 返回值为0,则初始化成功,若不为0则为失败。
- 2.WSACleanup 函数
- 这是 Socket 环境的退出函数,函数原型:
- int WSACleanup (void);
- 返回值:
- 返回值为0表示成功,SOCKET_ERROR 表示失败。
- 3.socket 函数
- socket 套接字的创建函数,函数原型:
- SOCKET socket(int af, int type, int protocol );
- 第一个参数为:int af, 代表网络地址族,目前只有一种取值有效,即 AF_INET, 代表 internet 地址族;
- 第二个参数为:int type, 代表网络协议类型, SOCK_DGRAM 代表 UDP 协议, SOCK_STREAM 代表 TCP 协议。
- 第三个参数为:int protocol,指定网络地址族特殊协议,目前无用,赋值0即可。
- 返回值:
- 返回值为 SOCKET, 若返回INVALID_SOCKET 则失败。
- 4.bind 函数
- 用于将套接字绑定到一个已知地址上,函数原型:
- int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);
- 第一个参数为:SOCKET s, 指定将被绑定的套接字。
- 第二个参数为:SOCKADDR_IN *name, 是一个sockaddr结构指针,该结构中包含了要绑定的地址和端口。
- 第三个参数为:int namelen, 确定第二个参数的结构长度。
- 返回值: 成功返回0,失败返回SOCKET_ERROR。
- 下面对其涉及的类型作一番解析:
- sockaddr 类型:
- sockaddr 类型是用来表示 Socket 地址的类型,同上面的 socketaddr_in 类型相比,sockaddr 的适用范围更广,
- 因为sockeaddr_in只适用于 TCP/IP 地址。sockaddr 的定义如下:
- struct sockaddr
- {
- ushort sa_family;
- char sa_data[14];
- };
- 可知sockaddr 的16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockadddr的。
- 事实上也往往使用这种方法。
- sockaddr_in 定义了socket发送和接收数据包的地址,其定义如下:
- strucr sockaddr_in
- {
- short sin_family;
- u_short sin_port;
- struct in_addr sin_addr;
- char sin_zero[8];
- };
- 其中 in_addr 定义如下:
- struct in_addr
- {
- union
- {
- struct {u_char s_b1, s_b2, s_b3, s_b4} S_un_b;
- struct {u_short s_w1, s_w2} S_un_w;
- u_long S_addr;
- }S_un;
- };
- 首先阐述 in_addr 的信义。
- 很显然它是一个存储 ip 地址的联合体,有三种表达方式:
- 第一种用四个字节来表示IP地址的四个数字;
- 第二种用两个双字节来表示IP地址;
- 第三种用一个长整型来表示IP地址;
- 给 in_addr 赋值的一种最简单方法是使用 inet_addr 函数, 它可以把一个代表IP地址的字符串赋值
- 转换为in_addr类型。如:
- addrServer.sin_addr = inet_addr("192.168.0.2");
- 其反函数是 inet_ntoa,可以把一个 in_addr 类型转换为一个字符串。
- sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下:
- 第一字段 short sin_family,代表网络地址族,如前所述,只能取值AF_INET;
- 第二字段 u_short sin_port, 代表IP地址端口,由程序员指定;
- 第三字段 struct in_addr sin_addr, 代表IP地址;
- 第四个字段char sin_zero[8],是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。
- 5.listen 函数
- 该函数让一个套接字在指定IP地址的指定端口处监听连接请求的到来,函数原型:
- int listen( SOCKET s, int backlog );
- 该函数使得一个进程可以接受其他进程的请求,从而成为一个服务器进程。
- 在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
- listen 函数一般在调用bind之后、调用accept之前调用。
- 返回值: 成功则返回0,失败返回SOCKET_ERROR,可以调用函数WSAGetLastError来取得错误代码。
- 6.accept函数
- 该函数从连接请求队列中获得连接信息,并创建新的套接字用于收发数据,实现服务器与客户端的通信。函数原型:
- SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
- 第一个参数:SOCKET s, 监听套接字
- 第二个参数:struct sockaddr addr, 存储请求连接的客户端IP地址、端口信息
- 第三个参数:int addrlen,第二个参数所占空间大小
- 返回值:
- 成功返回新套接字,失败返回错误信息
- 7.connect 函数
- 向指定的网络主机请求连接,函数原型:
- int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
- 第一个参数:SOCKET s, 客户端用于收发数据的套接字。
- 第二个参数:struct sockaddr *name, 指定网络主机IP地址和端口号。
- 第三个参数:int namelen, 第二参数长度
- 返回值:
- 成功返回0,失败返回-1。
- 8.sendto、recvfrom、send、recv函数
- 在 Socket 中有两套发送和接收函数。一是sendto 和recvfrom; 二是send 和 recv。
- 前一套在函数参数中要指明地址(UDP协议),
- 而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。
- 函数原型:
- int sendto( SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
- int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);
- int send(SOCKET s,const char FAR *buf, int len, int flags);
- int recv(SOCKET s, char FAR *buf, int len, int flags);
- 第一个参数: 套接字
- 第二个参数: 数据指针
- 第三个参数: 数据长度
- 第四个参数: 收发数据方式的标识,如果不需要特殊要求可以设置为0,其他值可参考MSDN;
- 第五个参数: 目标主机地址
- 第六个参数: 地址的长度
- 返回值: 运行成功则返回收发数据的字节数,失败返回SOCKET_ERROR
- 9.closesocket 函数
- 关闭套接字,函数原型:
- int closesocket( SOCKET s );
- 返回值: 成功返回0,失败返回SOCKET_ERROR。
from:http://blog.csdn.net/ltag0110rtag/article/details/7390304