WinSock-EchoCInt程序学习
2017-11-23 15:23
197 查看
//写在前面,该系列是对《WinSock网络编程经络》的学习笔记。
//写写博客,码码代码,每天积累一点。
WinSock有两个版本,版本1.1–>winsock.h;版本2.2–>winsock2.h。版本2.2兼容1.1的实现,所以一般直接使用winsock2.h。而且winsock2.h中包含Windows.h头文件,所以不需要在包含Windows.h头文件。
这里接触到了新的与编译命令:#pragma。这里有网上找到的一些关于这个命令的资料:#pragma命令详解(一)这篇博客说的很详细。但目前需要用到的comment命令的5个预定义标识符hi一lib。lib就是在目标文件中放入库搜索记录,需要有commentstring参数,其实就是指需要链接器搜索的库文件。需要注意的是,它优先于默认的设置重点搜索记录。这行代码意思就是链接ws2_32.lib。
这里接触到了#define宏定义命令。同样,这里有网上找到的关于这个命令的资料:#define详解这篇博客中介绍了#define的变体#ifdef、#ifndef等,很有用,在这里标记一下,以后遇到了可以回过头来查询。这里用到的就是简单的常量宏定义。好处有:
1.能让程序清晰明确,常量一眼便知;
2.让程序便于修改,修改宏定义处常量即可修改程序中各处数值。
这就是熟悉的int main(int argc, char *argv){}首先,argc是命令行总的参数,argv[]是argc个参数,其中,argv[0]是程序的名字(就是我们自己定义的程序名),argv[1]到argv[argc-1]程序运行时,用户从命令行中输入的参数。
本程序设定2个参数,一个程序本身名字,即argv[0],另一个为服务器地址,若有第三个参数,设为服务器端口号,若没有则使用默认端口号(ECHO_DEF_PORT 7)
这里是使用WSAStartup初始化WinSock动态链接库,它必须是被应用程序调用的第一个WinSock函数。
客户端与服务器建立连接时需要设定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的预处理器命令,让编译器别报错。我是采用后者解决的。
网络通信的第一步就是创建一个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获得具体的错误代码。
这里通过connect()建立与服务器的连接:
第一个参数就是之前返回标识socket的句柄echo_soc;
第二个参数是服务器的地址,由于函数形参与实参不一致,形参是通用的socket地址结构指针–>struct sockaddr *,所以这里要进行强制类型转换,即取serv_addr的引用;
第三个参数是地质结构的长度,通过sizeof()函数获取serv_addr实现。
发送数据用send,接收数据用recv。第一个参数都是socket句柄echo_soc;第二个参数,send的是数据缓冲区,recv是接收缓冲区;第三个参数,send的是要传输数据的长度,recv的是缓冲区的长度。
进一步挖掘send与recv函数的使用可以参考这篇Socket send函数与recv函数详解。
这里要注意recv_buf[result]=0;这一句说明的是什么意思?个人觉得,result是send和recv函数的返回值,而当这两个函数执行成功时,返回的是发送或者接收的数据大小。而将数据大小作为recv_buf[]的参数,应该是test_data最后一位表示终止符“/0”,所以才会用这样一句来判断。
这只是目前个人的想法,后面会多学点东西再回过头来验证一下。
一定要及时关闭连接并释放WinSock的资源。
EchoCInt.exe 127.0.0.1
应该会收到如下结果
[Echo Client] receives:”Hello World!”
这个程序其实挺简单,但确实能初步接触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编程,接下来会编写服务器程序,到时候要结合起来看看。
相关文章推荐
- WinSock网络编程学习(三)判断主机字节序程序
- WinSock网络编程学习笔记(八):测试bind程序
- 学习API HOOK,编写了一个winsock 的封包抓取程序,可免费使用;
- WinSock网络编程学习笔记(六):服务查询程序(getserverbyname和getserverbyport函数)
- WinSock-EchoServ程序学习
- WinSock网络编程学习(一)Echo客户/服务器程序
- WinSock网络编程学习笔记(七):协议查询程序(getprotobyname和getprotobynumber函数)
- WinSock网络编程学习(二)计算校验和程序
- 关于程序学习自己的看法
- 20135337朱荟潼 Linux第七周学习总结——可执行程序的装载
- C编写Windows服务程序学习
- 鱼油账号记录程序 - 零基础入门学习Delphi38
- 学习程序的自我建议---从实际软件的源代码开始学习
- MPI学习笔记之并行程序概述
- 微信小程序学习总结(一)
- 利用python程序抓爬网页,获取城市楼市价格(Python学习实例二)
- openocd 下载程序到32开发板学习笔记
- VC++6.0调试程序学习
- Swift教程_通过改造官方Sample学习Swift(四)_swift重写Sample(搭建程序结构、构建storyboard)
- 二,零基础学习JavaScript-开始写我们的第一JavaScript程序(1)