您的位置:首页 > 其它

WinSock-EchoCInt程序学习

2017-11-23 15:23 197 查看
//写在前面,该系列是对《WinSock网络编程经络》的学习笔记。

//写写博客,码码代码,每天积累一点。

Echo客户程序:

Echo,直译回声,其实就是一个简单的网络通信调试和测量工具,Echo服务器将收到的任何信息发回给客户端。初学阶段利用TCP协议(当然也可以使用UDP协议),使用知名端口号7,来实现简单的Echo功能。

头文件包含:

#include <stdio.h>
#include <winsock2.h>


WinSock有两个版本,版本1.1–>winsock.h;版本2.2–>winsock2.h。版本2.2兼容1.1的实现,所以一般直接使用winsock2.h。而且winsock2.h中包含Windows.h头文件,所以不需要在包含Windows.h头文件。

链接库文件:

#pragma comment(lib,"ws2_32") /*WinSock使用的库函数*/


这里接触到了新的与编译命令:#pragma。这里有网上找到的一些关于这个命令的资料:#pragma命令详解(一)这篇博客说的很详细。但目前需要用到的comment命令的5个预定义标识符hi一lib。lib就是在目标文件中放入库搜索记录,需要有commentstring参数,其实就是指需要链接器搜索的库文件。需要注意的是,它优先于默认的设置重点搜索记录。这行代码意思就是链接ws2_32.lib。

定义常量:

#define ECHO_DEF_PORT   7   /*定义默认的端口号*/
#define ECHO_BUF_SIZE   256 /*定义缓冲区大小256字节*/


这里接触到了#define宏定义命令。同样,这里有网上找到的关于这个命令的资料:#define详解这篇博客中介绍了#define的变体#ifdef、#ifndef等,很有用,在这里标记一下,以后遇到了可以回过头来查询。这里用到的就是简单的常量宏定义。好处有:

1.能让程序清晰明确,常量一眼便知;

2.让程序便于修改,修改宏定义处常量即可修改程序中各处数值。

启动函数:

int main(int argc, char *argv){
/*代码*/
}


这就是熟悉的int main(int argc, char *argv){}首先,argc是命令行总的参数,argv[]是argc个参数,其中,argv[0]是程序的名字(就是我们自己定义的程序名),argv[1]到argv[argc-1]程序运行时,用户从命令行中输入的参数。

命令行参数

if (argc < 2) {
printf("input %s server_address [port]\n", argv[0]);
return -1;
}
if (argc >= 3) {
port = atoi(argv[2]);
}


本程序设定2个参数,一个程序本身名字,即argv[0],另一个为服务器地址,若有第三个参数,设为服务器端口号,若没有则使用默认端口号(ECHO_DEF_PORT 7)

WinSock初始化

WSAStartup(MAKEWORD(2, 0), &wsa_data);


这里是使用WSAStartup初始化WinSock动态链接库,它必须是被应用程序调用的第一个WinSock函数。

指定地址和端口号

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);


客户端与服务器建立连接时需要设定socket中一些信息:服务器的地址和端口号,那么这些信息被放在一个struct sockaddr_in中,这个数据结构中有:

sin_family:地址族,Internet地址族都是用AF_INET标识;

sin_port:16位的端口号,注意要转换成网络字节序;

sin_addr:32位的IP地址,即服务器地址,通过获取命令行中第一行输入,由于命令行输入是ASCII格式,所以通过inet_addr转换成二进制格式地址。

需要注意的是VS2013后对旧的int_addr进行了更改,所以运行时会报错,提示要么更换为inet_pton() or InetPton(),或者添加 _WINSOCK_DEPRECATED_NO_WARNINGS的预处理器命令,让编译器别报错。我是采用后者解决的。

创建套接口:

echo_soc = socket(AF_INET, SOCK_STREAM, 0);


网络通信的第一步就是创建一个socket,调用socket()函数,接受三个参数:

1.AF_INET,指明通信使用的地址族,该参数代表TCP/IP地址,常用的还有AF_UNIX代表UNIX地址;

2.SOCK_STREAM是socket的类型,提供面向连接的全双工服务,若使用UDP协议,设定数据报套接口,使用SCOK_DGRAM;;

3.使用具体的协议,由于只有TCP提供的是数据流服务,所以,2中使用的是SOCK_STREAM,就不需要指定具体的协议,参数设为0即可。

成功返回的是标识socket的句柄,即这里定义的echo_soc;失败返回INVALID_SOCKET,后面可以通过WSAGetLastError获得具体的错误代码。

建立连接:

result = connect(echo_soc, (struct sockaddr *)&serv_addr, sizeof(serv_addr));


这里通过connect()建立与服务器的连接:

第一个参数就是之前返回标识socket的句柄echo_soc;

第二个参数是服务器的地址,由于函数形参与实参不一致,形参是通用的socket地址结构指针–>struct sockaddr *,所以这里要进行强制类型转换,即取serv_addr的引用;

第三个参数是地质结构的长度,通过sizeof()函数获取serv_addr实现。

发送和接收:

if (result == 0) {
result = send(echo_soc, test_data, send_len, 0);
result = recv(echo_soc, recv_buf, ECHO_BUF_SIZE, 0);
}


发送数据用send,接收数据用recv。第一个参数都是socket句柄echo_soc;第二个参数,send的是数据缓冲区,recv是接收缓冲区;第三个参数,send的是要传输数据的长度,recv的是缓冲区的长度。

进一步挖掘send与recv函数的使用可以参考这篇Socket send函数与recv函数详解

显示信息:

if (result > 0) {
recv_buf[result] = 0;
printf("[Echo Clinet] receives: \"%s\"\r\n", recv_buf);
}
else
printf("[Echo Client] error : %d.\r\n", WSAGetLastError());


这里要注意recv_buf[result]=0;这一句说明的是什么意思?个人觉得,result是send和recv函数的返回值,而当这两个函数执行成功时,返回的是发送或者接收的数据大小。而将数据大小作为recv_buf[]的参数,应该是test_data最后一位表示终止符“/0”,所以才会用这样一句来判断。

这只是目前个人的想法,后面会多学点东西再回过头来验证一下。

关闭连接:

closesocket(echo_soc);
WSACleanup();


一定要及时关闭连接并释放WinSock的资源。

运行结果:

刚开始直接生成项目然后就F5运行了,但命令行窗口显示一下就消失了,仔细一看发现还需要事先打开服务器程序。然后才能在客户端程序输入:

EchoCInt.exe 127.0.0.1

应该会收到如下结果

[Echo Client] receives:”Hello World!”

写在最后:

将之前所有综合起来会得到完整的程序:

#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32") /*WinSock使用的库函数*/

#define ECHO_DEF_PORT 7 /*宏定义默认连接端口号*/
#define ECHO_BUF_SIZE 256 /*宏定义缓冲区大小*/

int main(int argc, char **argv) {
WSADATA wsa_data;
SOCKET echo_soc = 0; /*创建socket句柄*/
struct sockaddr_in serv_addr; /*创建服务器地址结构体*/
unsigned short port = ECHO_DEF_PORT;
int result = 0, send_len = 0;
char *test_data = "Hello World!", recv_buf[ECHO_BUF_SIZE];

if (argc < 2) {
printf("input %s server_address [port]\n", argv[0]);
return -1;
}

if (argc >= 3) {
port = atoi(argv[2]);
}

WSAStartup(MAKEWORD(2, 0), &wsa_data); /*初始化WinSock资源*/
send_len = strlen(test_data);

/*服务器地址参数设定*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

if (serv_addr.sin_addr.s_addr == INADDR_NONE) {
printf("[ECHO] invalid address\n");
return -1;
}

/*创建socket*/
echo_soc = socket(AF_INET, SOCK_STREAM, 0);
result = connect(echo_soc, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

/*连接成功*/
if (result == 0) { result = send(echo_soc, test_data, send_len, 0); result = recv(echo_soc, recv_buf, ECHO_BUF_SIZE, 0); }

if (result > 0) { recv_buf[result] = 0; printf("[Echo Clinet] receives: \"%s\"\r\n", recv_buf); } else printf("[Echo Client] error : %d.\r\n", WSAGetLastError());

/*关闭资源*/
closesocket(echo_soc); WSACleanup();

return 0;
}


这个程序其实挺简单,但确实能初步接触WinSock编程,接下来会编写服务器程序,到时候要结合起来看看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: