您的位置:首页 > 理论基础 > 计算机网络

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函数进行接收。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: