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

Socket概述及TCP/IP的C++实现

2014-02-18 18:47 302 查看
网络通信实际是应用进程之间的通信,而要完整的描述一个应用进程在网络中的位置必须用 IP+端口;

Socket就是一种在网络中进行数据通信的一种抽象描述。它是一种协议,本地地址,本地端口的抽象。





Socket它是面向C/S模型而设计的。

Windows Sockets 规范,又称为WinSock,是微软联合其他几家公司推出的Windows 操作系统环境下的网络编程接口。它继承了UNIX下的Socket,是Windows下标准、通用的TCP/IP编程接口。

目前推出了1.1版本,这个版本只支持TCP/IP协议;2.x以后,支持更多的网络与协议规范。比如无线网络通信等。

版本 1.1 头文件 WINSOCK.h 链接库文件 wsock32.lib 动态库文件 Winsock.dll

版本 2.2 头文件 WINSOCK2.h 链接库文件 ws2_32.lib 动态库文件 WS2_32.dll

Winsock 提供了两种形式的Socket:流式套接字(stream socket)和 数据报套接字(datagram socket)。其中,流式套接字只TCP协议,数据报套接字支持UDP协议。

以下是基于套接字的TCP协议实现过程:

基于流式套接字的编程模式如下:





基于数据报套接字的编程模式如下:





下面这个是一个特例,也就是说在UDP中也可以由connect(),通过connect()将本地的UDP 的socket与远程的socket连接起来。

4、“有连接”的UDP

虽然UDP是无连接的,但是也可以通过调用connect()将本地的UDP socket FD与一个远程的UDP socket FD连接起来——只需要指定这个远程sockFD的地址,假设这个地址是sockaddr_in remoteSockAddr,代码如下:

if (connect(sockFD,

(sockaddr*)&remoteSockAddr,

sizeof(remoteSockAddr)) < 0) {

sockClass::error_info("connect() failed.");

}

复制代码

建立连接后的UDP RecvQ就不会将非来自remoteSockAddr的数据包收入。

请注意UDP的connect()与TCP的 connect()很不相同,TCP是连接服务器的监听socket,并且会阻塞直到服务器调用accept()。一般的说法,UDP的连接并不会改变 UDP的各种特点,比如,即使连接,UDP也不知道远程主机是否在线连接或者是否断开——但是,我个人认为,改变了本机的RecvQ接收数据包的过滤机制,也就改变了UDP原本可以接收来自任何地址信息的属性。

如果希望断开UDP的连接,需要使用一个特定的“断开”地址,代码如下:

sockaddr descon_sock_addr;

memset(&descon_sock_addr, 0, sizeof(descon_sock_addr));

descon_sock_addr.sa_family = AF_UNSPEC;

if (connect(sockFD,

&descon_sock_addr,

sizeof(descon_sock_addr)) < 0) {

sockClass::error_info("des connect() failed.");

}

复制代码

请注意这里的地址族AF_UNSPEC直接赋值给了一个sockaddr结构。我试过,使用sockaddr_in也是可以的,但是无论是哪个结构,首先都得将整个结构对象清零,否则可能报错。
注:我们可以利用WinSock API函数也可以利用MFC提供的WinSock封装类。

有一点小疑惑,网络编程中为何不需要客户端IP地址及端口号即:不需要客户端套接字地址。

因为,我们在计算机网络中,我们在传数据时,它会自动带上本机的IP地址。比如,客户端向服务器端发送某个

数据报时,它肯定会在报头加上源IP地址,而目的IP地址及端口号这个就需要自己手动加入数据报中,不然,当路由器进行解析

的时候,就不知道该往哪个网络里面的主机里面传送了。

根据上面的介绍,我想大家一定想练练手,好,下面我将自己的demo提供给大家。最后会提供给大家一个链接的。

废话不多说,直接看结果:以下是UDP传输结果,上面是服务器端程序,显示的数据是从客户端发过去的。

第二个界面与上面相反,客户端程序,显示的数据从服务器端发过来的。当然,我们在实际编程时,可以有的放矢,

有时候不需要回传数据进行验证之类的。不是双工模式,而是单工模式。





以下是TCP的执行结果:

先打开服务器端,显示如下:





再开启客户端,显示如下:





不多解释,一切全在代码中。

socketserver.cpp文件:

// socketserver.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "conio.h"
#include "windows.h"
//socket头文件
#include "winsock.h"
//socket库的lib
#pragma comment(lib,"ws2_32.lib")

void TCPServer()
{
/***************创建服务器端套接字SOCKET*******************/
/*******socket()函数解释:IP协议族,数据流方式,TCP协议****/
SOCKET socksvr=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == socksvr)
{
return;
}
/*************建立服务器端套接字地址***********************/
/********************绑定IP和端口号******************/
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;//代表internet协议族
/**htons()函数解释:是将u_short型变量从主机字节顺序变换为TCP/IP网络字节顺序**/
/**这里涉及大小端系统问题。intel处理器是低位字节在****************/
/**较低地址存放,而高位字节在较高地址存放,与网络字节顺序相反,故需要调换过来****/
svraddr.sin_port = htons(5678);
//htonl()函数是将u_long型变量从主机字节顺序变为TCP/IP网络字节顺序。
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//此宏为0,当前机器上任意IP地址,也可以指定当前机的ip和端口。
//绑定,将服务器端套接字与服务器端套接字地址绑定
bind(socksvr,(struct sockaddr *)&svraddr,sizeof(svraddr));//指定名字,类型,长度。绑定套接字。
//侦听
listen(socksvr,SOMAXCONN);//第一个参数是套接字,第二个参数是等待连接队列的最大长度。
//等候客户端建立连接
printf("等候客户端.......\n");
//建立客户端套接字地址,主要是为了接收客户端返回参数之用
struct sockaddr_in clientaddr = {0};
int nLen = sizeof(clientaddr);
//以下是建立客户端套接字并建立连接函数。有一个确认的过程。
//注:后面填的是客户端地址长度的地址。
SOCKET sockclient = accept(socksvr,(struct sockaddr*)&clientaddr,&nLen);//建立连接函数
printf("客户端已连接\n");
/********以下是数据收发部分*********/
//先接收后发送,由上面知,数据已在sockclient中,我们只需读此结构便可知晓数据
CHAR szText[100] = {0};
//接收缓冲区数据
recv(sockclient,szText,100,0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。
printf("%s\n",szText);
CHAR szSend[100] = "Hello Client";
send(sockclient,szSend,sizeof(szSend),0);//发送函数。
/****accept/recv/send 都是堵塞函数,需要把所以的数据都接收完或发送完才可以工作。*****/
// getch();//暂停一下
//关闭socket
closesocket(sockclient);
closesocket(socksvr);

}

void UDPServer()
{
//创建socket
SOCKET socksvr = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(INVALID_SOCKET == socksvr)
{
return ;
}
//服务器套接字地址
//绑定ip与端口,先定义端口等一些信息。
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(5780);
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
bind(socksvr,(struct sockaddr*)&svraddr,sizeof(svraddr));

/********以下是数据收发部分*********/
//客户端套接字地址,接收客户端数据时需要用,数据都在套接字里面。
CHAR szRecv[100] = {0};
struct sockaddr_in clientaddr = {0};
int nLen = sizeof(clientaddr);
/*下面函数前四个参数同TCP接收数据函数recv()一样,后两个中,一个是返回发送*******/
/*数据地址的主机的地址,包括IP地址以及端口号,最后一个为地址长度的地址。*******/
/*此函数中,先是服务器端的套接字,后是客户端的地址*/
//从后往前读此函数
recvfrom(socksvr,szRecv,100,0,(struct sockaddr*)&clientaddr,&nLen);//构造ip地址
printf("%s\n",szRecv);

//注1:该程序也可以向客户端发送数据。
//注2:服务器端中,必须也是先接收后发送,不然,我们无法知道客户端的地址。下面函数中clientaddr已知晓
CHAR szSend[100] = "hello udp client";
//从前往后读此函数
sendto(socksvr,szSend,100,0,(struct sockaddr*)&clientaddr,nLen);//发送时构造ip地址和端口。

//    getch();//可以暂停显示,这个很重要。

//关闭socket
closesocket(socksvr);

}

int main(int argc, _TCHAR* argv[])
{
//初始化socket库
WSADATA wsa = {0}; //WinSockApi 取WSA+DATA组成套接字结构体
WSAStartup(MAKEWORD(2,2),&wsa);
//服务器
TCPServer();
//UDPServer();
//清理套接字资源
WSACleanup();
getch();//暂停一下

return 0;
}


socketclient.cpp 文件:

// socketclint.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "conio.h"
#include "windows.h"
#include "winsock.h"
#pragma comment(lib,"ws2_32.lib")

void TCPClient()
{
//创建socket
SOCKET sockclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == sockclient)
{
return;
}
//连接服务器,建立服务器端套接字地址
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(5678);
//对于inet_addr()函数,它是把“xxx.xxx.xxx.xxx”形式表示的IPV4地址,转换为IN_ADDR结构体能够
//接收的形式(unsigned long型,因为IN_ADDR结构体中的负责接收的S_addr成员变量的类型是unsigned long型)
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本机ip

//向服务器发出连接请求,当然我们也可以通过connet函数的返回值判断到底有无连接成功。
int iRetVal = connect(sockclient,(struct sockaddr*)&addr,sizeof(addr));
if(SOCKET_ERROR == iRetVal)
{
printf("服务器连接失败!");
closesocket(sockclient);
return;
}
printf("服务器连接成功!\n");
//数据收发
CHAR szSend[100] = "hello server";   //客户端  先发后收
send(sockclient,szSend,sizeof(szSend),0);  //发送函数,可以通过返回值判断发送成功与否。

//接收服务器回传的数据
CHAR szRecv[100] = {0};
recv(sockclient,szRecv,100,0); //接收函数
printf("%s\n",szRecv);//表示以字符串的格式输出服务器端发送的内容。

//  getch();//暂停一下
//关闭socket
closesocket(sockclient);
}
void UDPClient()
{
//创建SOCKET ,ip协议族,数据报方式,udp协议。
SOCKET sockclient = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(INVALID_SOCKET == sockclient)
{
return ;
}
//数据收发,服务器端套接字地址
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(5780);
svraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定服务器端的IP与端口。
CHAR szSend[100] = "hello udp server";
/*此函数先是客户端的套接字,然后是服务器端地址*/
//简单理解为:从函数前面的客户端套接字的发送数据缓存区中将数发送给服务器端地址
sendto(sockclient,szSend,100,0,(struct sockaddr*)&svraddr,sizeof(svraddr));//发送时构造ip地址和端口。

//注:该程序也可以接收服务器端回传的数据。
CHAR szRecv[100];
//简单理解为:从函数后面的服务器端地址中取数到客户端套接字的接收缓冲区szRecv中
int len = sizeof(svraddr);
recvfrom(sockclient,szRecv,100,0,(struct sockaddr*)&svraddr,&len);
printf("%s \n",szRecv);
//关闭socket
closesocket(sockclient);
}

int main(int argc, _TCHAR* argv[])
{
//初始化socket库
WSADATA wsa = {0};
WSAStartup(MAKEWORD(2,2),&wsa);
//tcp客户端
TCPClient();
//UDPClient();
//清理套接字资源
WSACleanup();
getch();

return 0;
}


已传至PUDN,文件名1_1-socket,用户可自行下载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: