TCP/IP(7)——网络编程(1)基础函数介绍
2019-05-05 23:33
435 查看
今天介绍最简单的客户机/服务器程序demo
我们将了解到网络编程的大概步骤,在win32下需要使用哪些函数。(函数参数不用记,考试考代码改错,改代码注释,关闭资源什么的)
解释:该图为TCP的最简单实现。
话不多说,直接看客户机和服务器的demo(备注看懂就行):
客户端:
[code] #include "stdafx.h" #include <Winsock2.h>//win32的库加载 #include <stdio.h> #pragma comment(lib, “ws2_32.lib”); //使用静态链接库 /* 动态数据库和静态数据库都是共享代码的一种方式 静态数据库(.lib)的代码转载速度快,执行速度比动态数据库快。因为一开始就调用了整个静态链接库。 而且只要保证开发者的计算机有正确的.LIB文件,在以二进制形式发布程序的时候就不需要考虑在用户的计算机上,。lib文件是否存在以及其版本问题。 动态数据库更加的节省内存,因为仅仅调用自己需要的,不需要的就不需要调用。dll文件和exe文件独立,只要输出的接口不变(名字,参数,返回类型) 那么,更换DLL不会对exe操作影响,极大提高了可维护性和可拓展性。 不同编程语音只要遵循函数调用约定,则可以调用同一个DLL函数 总结:DLL适合大型软件开发,但是缺点是DLL的应用程序不是自完备的,需要依赖DLL,不存在DLL则会提示“缺少XXX.dll,程序无法运行” */ //服务器端口号为5050 #define DEFAULT_PORT 9999 #define DATA_BUFFER 1200 int main(int argc, char* argv[]) { WSADATA wsaData;//协议栈结构体 SOCKET sClient;//客户端的socket结构体 int iPort = DEFAULT_PORT; //从服务器端接收的数据长度 int isendLen, irecvLen; //接收数据的缓冲 char buf[DATA_BUFFER]; //服务器端地址 struct sockaddr_in ser;//INET协议族地址结构(sockaddr_in) /*使用前进行清零,以养成良好习惯 使用下面两个函数实现 void ZeroMemory(PVOID destination, SIZE_T length); void memset(void *dest, int c, size_t count); */ //判断输入的参数是否正确 if (argc < 2) { //提示在命令行中输入服务器IP地址 printf("Usage:client [server ip address]\n"); return -1; } //接收数据的缓冲区初始化 memset(buf, 0, sizeof(buf)); //1.windows socket初始化结构体( WSAData ) if (WSAStartup(MAKEWORD(2,2), &wsaData)!=0) {//MAKEWORD(2,2)代表使用2.2的版本 printf("Failed to load Winsock.\n"); return -1; } //填写要连接的服务器地址信息 ser.sin_family = AF_INET; ser.sin_port = htons(iPort); //inet_addr()函数将命令行的点分IP地址转换为用二进制表示的网络字节顺序的IP地址 ser.sin_addr.s_addr = inet_addr(argv[1]); //客户端不用绑定端口,操作系统会自动分配,但是有些服务需要,比如DHCP就需要使用68端口绑定 //建立客户端流式套接口 sClient = socket(AF_INET, SOCK_STREAM, 0); /*socket是用来创建套接字的 下面三段代码分别创建TCP、UDP、原始套接口(比如ICMP) SOCKET sock = socket(AP_INET,SOCK_STREAM,0);建立客户端流式套接口 tcp SOCKET sock = socket(AP_INET,SOCK_DGRAM,0);建立客户端数据报套接字 udp SOCKET sock = socket(AP_INET,SOCK_RAM,IPPROTO_ICMP);建立客户端原始数据包套接字 */ if (sClient == INVALID_SOCKET) { printf("socket() Failed:%d\n", WSAGetLastError()); WSACleanup(); return -1; } //请求与服务器端建立TCP连接 /* Connect连接请求,可以用于面向连接套接口,也可用于无连接套接口(无三次握手) 面向连接:触发调用端主动进行TCP三次握手, 其结果通常是成功连接、 WSAETIMEDOUT(多次发送SYN报文,始终未收到回复)、 WSAECONNREFUSED(目标主机返回TCP-RST)。 只有在握手以后才会返回结果,因此,通常也是阻塞挂起 面向非连接:仅仅是在该套接口上形成与目标地址的对应关系,没有触发报文交互。(无三次握手) 客户端在connect之后可以直接用send发送,无需通过 sendto进行发送 */ if (connect(sClient, (struct sockaddr *)&ser, sizeof(ser)) == INVALID_SOCKET) { printf("connect() Failed:%d\n", WSAGetLastError()); closesocket(sClient); WSACleanup(); return -1; } while (1) { isendLen = send(sClient, argv[2], strlen(argv[2]), 0); if (isendLen == SOCKET_ERROR) { printf("send() Failed:%d\n", WSAGetLastError()); break; } //从服务器端接收数据 有连接数据接收 irecvLen = recv(sClient, buf, sizeof(buf), 0); /* recv函数只接收来自已连接的源端地址的数据, 虽然每次都能直接读取到该缓 冲区中的所有数据, 但最后可拷贝的数据受到应用程序缓冲区大小的限制 有下面两种情况: ① 协议接收正在接收数据则等待协议接收完成。 接收完成后如果recv缓冲区中有数据,则直接开始将缓冲区中的数据拷贝至应用程序缓冲区中, 如果应用程序缓冲区不够大,则需要进行多次拷贝; ② 如果recv缓冲区中无数据,如果又是阻塞状态则一直等到有数据后才返回,如果是非阻塞状态,则直接返回 无连接数据发送:Recvfrom recvfrom函数通常用于无连接套接口(如UDP),从指定目标地址接收数据报。 当后面两个参数置为NULL,实际等同于recv函数 */ if (irecvLen == 0) { break; } else if (irecvLen == SOCKET_ERROR) { printf("recv() Failed:%d\n", WSAGetLastError()); break; } else { printf("recv() data from server:%s\n", buf); } Sleep(2000); } closesocket(sClient); WSACleanup(); return 0; }
服务器:
[code]// cs_test.cpp : Defines the entry point for the console application. #include "stdafx.h" #include <stdio.h> /* <>引用的是编译器的类库路径里面的文件,编译器查找的时候,会在编译器的安装目录的标准库中开始查找, ””引用的是你程序 目录的相对路径中的头文件,如果找不到,再找编译器的类库路径。 */ #include <WINsock2.h> //不区分大小写 #include <stdlib.h> #define DEFAULT_PORT 5050 //设置服务器监听的端口号 int main(int argc, char* argv[]) { int iPort = DEFAULT_PORT; WSADATA wsaData;//协议栈结构体 SOCKET sListen,//服务器监听请求的socket结构体 sAccept;//服务器接受请求的socket结构体 //客户端地址长度 int iLen; //发送的数据长度 int iSend; //要发送给客户端的信息 char buf[] = "Hello, I am a server."; //服务器和客户端的IP地址 struct sockaddr_in ser, cli;//INET协议族地址结构(sockaddr_in) /*使用前进行清零,以养成良好习惯 使用下面两个函数实现 void ZeroMemory(PVOID destination, SIZE_T length); void memset(void *dest, int c, size_t count); */ printf("-------------------------------------\n"); printf("Server waiting\n"); printf("-------------------------------------\n"); //加载Winsock if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {//MAKEWORD(2,2)代表使用2.2的版本 printf("Failed to load Winsock.\n"); return -1; } //创建服务器套接口 sListen = socket(AF_INET, SOCK_STREAM, 0); /*socket是用来创建套接字的 下面三段代码分别创建TCP、UDP、原始套接口(比如ICMP) SOCKET sock = socket(AP_INET,SOCK_STREAM,0);建立客户端流式套接口 tcp SOCKET sock = socket(AP_INET,SOCK_DGRAM,0);建立客户端数据报套接字 udp SOCKET sock = socket(AP_INET,SOCK_RAM,IPPROTO_ICMP);建立客户端原始数据包套接字 */ if (sListen == INVALID_SOCKET) { printf("socket() Failed:%d\n", WSAGetLastError()); WSACleanup(); return -1; } //以下建立服务器端地址 ser.sin_family = AF_INET; //htos()函数把一个双字节的主机字节顺序的数据转换为网络字节顺序的数 ser.sin_port = htons(iPort); //htol()函数把一个四字节的主机字节顺序的数据转换为网络字节顺序的数 ser.sin_addr.s_addr = htonl(INADDR_ANY); /* bind函数一般用于服务器设置,客户端一般不关心自己的地址,而且端口号一般为随机分配, 所以一般不用绑定,如果需要指导端口号则需要绑定,比如 DHCP客户端请求报文。 */ if (bind(sListen, (LPSOCKADDR)&ser, sizeof(ser)) == SOCKET_ERROR) {//设置本地的地址(IP+端口号)与已创建的套接口联系起来 printf("bind() Failed:%d\n", WSAGetLastError()); closesocket(sListen); WSACleanup(); return -1; } /* TCP套接口有主动套接口和被动套接口之分,当调用socket函数创建一个流 SOCKET时,系统会假设该接口为主动,这时可以调用connect函数以主动发起连接, 这就是客户端SOCKET;如果调用的是listen函数,那么系统就将该SOCKET改为被 动,这就是服务器SOCKET。 */ if (listen(sListen, 5) == SOCKET_ERROR) { //接收客户端的连接请求(只有TCP的服务器端使用) 5是最大的队列长度值 //系统为每一个监听套接口维护一个待处理连接的队列,该队列由两个子队列组成,未完成连接队列和已完成连接队列,区分的标准是是否已完成TCP三步握手 printf("listen() Failed:%d\n", WSAGetLastError()); closesocket(sListen); WSACleanup(); return -1; } //初始化客户端地址长度参数 iLen = sizeof(cli); //进入一个无限循环,等待客户的连接请求 while (1) { sAccept = accept(sListen, (struct sockaddr *)&cli, &iLen);//接受客户端发出的connect请求 if (sAccept == INVALID_SOCKET) { printf("accept() Failed:%d", WSAGetLastError()); break; } //输出客户IP地址和端口号 printf("Accept client IP:[%s], port:[%d]\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port)); //给建立连接的客户端发送信息---有连接数据发送 iSend = send(sAccept, buf, sizeof(buf), 0); /* send函数仅仅是将数据拷贝到send缓冲区中,最后实际是由协议自动进行发送。 send返回值将受到套接字阻塞和非阻塞模式、发送的字节长度影响。 无连接数据发送:sendto、 sendto函数通常用于无连接套接口(如UDP),向指定目标地址发送数据报 即使该套接口已经调用了connect函数,传送该报文的目标地址仍然由to参数决定。 (优先级比connect的高) 对于一个面向连接的套接口,参数to和tolen将被忽略,sendto等价于send。 */ if (iSend == SOCKET_ERROR || iSend == 0) { printf("send() Failed.%d\n", WSAGetLastError()); closesocket(sAccept); break; } /* */ printf("send() byte:%d\n", iSend); printf("-------------------------------------\n"); closesocket(sAccept); } closesocket(sListen);//关闭套接口 WSACleanup();//终止使用Winsock(WSACleanup) return 0; }
问题:
1. 客户端和服务器端之间如何才能建立连接,需要经过几 个什么步骤?
2. 服务器端通过什么函数可以取出连接的客户端地址信息?
3. 服务器端如何发送,客户端又是如何接收呢,通过什么 函数?
答案:
1.服务器端通过WSATartup函数加载协议栈后,利用socket函数创建套接 字,并bind监听的端口号后利用listen函数进行监听。客户端同样利用 WSATartup函数加载协议栈并用socket函数创建套接字后利用connect 函数发起三次握手。最后,服务器端通过accept函数使连接建立成功。
2.服务器可以通过accept函数的第二个和第三个参数取出对端的客户端信 息,也可以通过getpeername函数取出连接的客户端信息。
3.通过send进行发送,通过recv进行接收,默认都为挂起状态。 UDP下可以通过sendto函数进行发送recvfrom函数进行接收。
相关文章推荐
- Linux网络编程基础--初等网络函数介绍(TCP)及示例程序
- Java基础—网络编程【OSI/RM TCP/IP】【网络通信三要素】【UDP传输 & TCP传输】【DNS域名解析】
- Java基础—网络编程【OSI/RM TCP/IP】【网络通信三要素】【UDP传输 & TCP传输】【DNS域名解析】
- TCP/IP基础介绍
- TCP/IP基础知识介绍
- Java基础—网络编程【OSI/RM TCP/IP】【网络通信三要素】【UDP传输 & TCP传输】【DNS域名解析】
- TCPIP完整的一套基础介绍
- TCP/IP完整的基础介绍
- TCP/IP完整的一套基础介绍
- TCP/IP编程基础——超时、多路复用、非阻塞
- JAVA基础之函数 随机数函数介绍
- C++基础学习之函数重载的简单介绍
- 【tcp-ip学习总结】socket编程基础/网络编程基础
- TCP/IP基础----TCP/IP四层模型
- 永不止步的网络基础1.2TCP/IP模型
- TCP/IP基础知识和原理
- TCP/IP 网络编程(五)
- TCP/IP编程之connect函数与accept函数的关系
- TCP/IP基础