WinSock-EchoCInt程序学习

来源:互联网 发布:网络统考报名修改密码 编辑:程序博客网 时间:2024/05/29 04:12

//写在前面,该系列是对《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编程,接下来会编写服务器程序,到时候要结合起来看看。

原创粉丝点击