网编(2):套接字的地址族和协议

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_40732350/article/details/88920332

函数:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功时返回文件描述符,失败时返回-1。
domain  //套接字中使用的协议族(Protocol Fam ily ) 信息。
type  //套接字数据传输类型信息。
protocol  //计算机间通信中使用的协议信息。

协议族( Protocol Family)——————————————

PF_INET     //IPv4互联网协议族
PF_INET6    //IPv6互联网协议族
PF_LOCAL    //本地通信的UNIX协议族
PF_pACKET   //底层套接字的协议族
PF_IPX      //IPX Novell 协议族

另外,套接字中实际采用的最终协议信息是通过socket函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数。

套接字类型( Type )——————————————

套接字类型指的是套接字的数据传输方式。

这里写图片描述

套接字类型1 : 面向连接的套接字(SOCK_STREAM)

可靠的、按序传递的、基于宇节的面向连接的数据传输方式的套接字。

传输方式特征:

  • 传输过程中数据不会消失。
  • 按序传输数据。
  • 传输的数据不存在数据边界(Boundary)。

套接字类型2: 面向消息的套接字(SOCK_DGRAM)

不可靠的、不按序传递的、以数据的高速传输为目的的套接字。

传输方式特征:

  • 强调快速传输而非传输顺序。
  • 传输的数据可能丢失也可能损毁。
  • 传输的数据有数据边界。
  • 限制每次传输的数据大小。

协议类型( protocol)——————————————

该参数决定最终采用的协议。

IPPROTO_TCP IPPTOTO_UDP IPPROTO_SCTP IPPROTO_TIPCTCP
TCP传输协议 UDP传输协议 STCP传输协议 TIPC传输协议

IPv4协议族中面向连接的套接字

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

IPv4协议族中面向消息的套接字

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

TCP套接字和UDP套接字不会共用端口号,所以允许重复。

怎么判断一个文件描述符是不是套接字描述符?

int issockettype(int fd)
{
	struct stat st;
	int err = fstat (fd, &st);
	if(err < 0){
		return -1;
	}
	if ((st.st_mode & S_IFMT) == S_IFSOCK){
		return 1;
	} else {
		return 0;
	}
}

 

1 地址族与数据序列

scoker结构体定义:

struct sockaddr_in
{
	sa_family_t sin_family;//地址族( Address Family )   4字节
	uint16_t sin_port;//16 位TCP/UDP 端口号             4字节
	struct in_addr sin_addr;//32 位IP                   4字节             
	char sin_zero[8);//不使用
};

成员sin_family
每种协议族适用的地址族均不同。

AF_LOCAL只是为了说明具有多种地址族而添加的。

成员sin_port
该成员保存16位端口号,重点在于它以网络字节序保存(关于这一点稍后将给出详细说明)。

成员sin_addr
该成员保存32位IP地址信息,且也以网络字节序保存。为理解好该成员,应同时观察结构体in_addr 。但结构体in_addr声明为uint32_t, 因此只需当作32位整数型即可。

成员sin_zero
无特殊含义。只是为使结构体sockaddr_ in的大小与sockaddr结构体保持一致而插入的成员。必需填充为0 , 否则无法得到想要的结果。后面会另外讲解sockaddr。

 

但是绑定函数没有直接使用该结构体,而是用了地址。

	SOCKADDR_IN servAddr;
//...
	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)//第二步
		ErrorHandling("bind() error");

//...

因为上面的第二个参数的结构如下,如果直接进行设置IP地址和端口号,不方便,所以就用了C语言的一个小技巧。

typedef struct sockaddr {
    ADDRESS_FAMILY sa_family;           // Address family.  4字节
    CHAR sa_data[14];                   // Up to 14 bytes of direct address.
} SOCKADDR;


typedef USHORT ADDRESS_FAMILY;
typedef unsigned short USHORT;

sockaddr_in是sockaddr继承而来。

sockaddr _in是保存IPv4地址信息的结构体。那为何还需要通过sin_family单独指定地址族信息呢?这与之前讲过的sockaddr结构体有关。结构体sockaddr并非只为IPv4设计,这从保存地址信息的数组sa_data长度为14 宇节也可看出。因此,结构体sockaddr要求在sin_family 中指定地址族信息。为了与sockaddr保持一致, sockaddr_ in结构体中也有地址族信息。

2 字节序(Order) 与网络字节序

CPU 向内存保存数据的方式有2种,这意味着CPU解析数据的方式也分为2种。

  • 大端序(Big Endian ): 高位字节存放到低位地址。
  • 小端序(Little Endian ): 高位字节存放到高位地址。

如果发送短用大端,接收短用小端,则会出问题,因此,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order), 非常简单一~统一为大端序。

即,先把数据数组转化成大端序格式再进行网络传输。因此,所有计算机接收的数据是网络字节序格式,小端序系统传输数据时应转化为大端序排列方式。

字节序转换( Endian Conversions)

unsigned short htons(unsigned  short);
unsigned short ntohs(unsigned  short);
unsigned long  htonl(unsigned  long);
unsigned long  ntohl(unsigned  long);

通过函数名应该能掌握其功能,只需了解以下细节。

  • htons中的h代表主机(host) 字节序。
  • htons 中的n代表网络(network) 字节序。

另外, s指的是short, l指的是long ( Linux 中Ion砓皂型占用4个字节,这很关键)。因此, htons是h 、to 、n 、s的组合,也可以解释为“把short型数据从主机宇节序转化为网络宇节序”。
如:ntobs可以解释为“把short型数据从网络宇节序转化为主机宇节序” 。

接收数据不用考虑字节序的问题。

则,除了向sockaddr_ in结构体变量填充数据外,其他情况无需考虑字节序问题。

字符串形式的IP地址转换成32位整数型数据:

#include <arpa/inet.h>
in_addr_t inet_addr(const char* string);

另一个函数利用了in_addr结构体,且其使用频率更高。

#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr * addr);

可以这样使用:

inet_aton(addr, &addr_inet.sin_addr);

字节序到字符串:

#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);

网络地址初始化步骤:

struct sockaddr_in addr;
char* serv_ip = "211.117.168.13";//声明IP 地址字符串
char * serv_port = "9190";//声明端口号字符串

memset(&addr, 0, sizeof(addr)); //结构体变量 addr 的所有成员初始化为 0
addr.sin_family = AF_INET;//指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip);//基于宇符串的IP 地址初始化
addr.sin_port = htons(atoi(serv_port));//基于宇符串的端口号初始化

INADDR_ANY

每次创建服务器端套接字都要输入IP地址会有些繁琐, 此时可如下初始化地址信息。

struct sockaddr_in addr;
char * serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

与之前方式最大的区别在于,利用常数INADDR_ANY分配服务器端的IP地址。若采用这种方式, 则可自动获取运行服务器端的计算机IP地址, 不必亲自输入。而且,若同一计算机中已分配多个IP地址(多宿主( Multi-homed ) 计算机, 一般路由器属于这一类), 则只要端口号一致,就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式。而客户端中除非带有一部分服务器端功能, 否则不会采用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

展开阅读全文

求助:通常每个套接字地址通常每个套接字地址(协议/地址/端口)只允许使用一次

05-11

rn“UDP_Server = new UdpClient(localPort); //创建一个新的端口号” 这一句这里,总是报错:通常每个套接字地址通常每个套接字地址(协议/网络地址/端口)只允许使用一次rn这是一个即时通讯的程序。rnrn[code=C#]using System;rnusing System.ComponentModel;rnusing System.Collections.Generic;rnusing System.Diagnostics;rnusing System.Linq;rnusing System.Text;rnusing System.Net.Sockets;rnusing System.Net;rnusing System.Windows.Forms;rnusing System.Threading;rnrnnamespace QQClassrnrn public partial class UDPSocket : Componentrn rn private IPEndPoint ServerEndPoint = null; //定义网络端点rn private UdpClient UDP_Server = new UdpClient(); //创建网络服务,也就是UDP的Socketsrn private System.Threading.Thread thdUdp; //创建一个线程rn public delegate void DataArrivalEventHandler(byte[] Data, IPAddress Ip, int Port); //定义了一个托管方法rn public event DataArrivalEventHandler DataArrival; //通过托管在控件中定义一个事件rn private string localHost = "127.0.0.1";rn [Browsable(true), Category("Local"), Description("本地IP地址")] //在“属性”窗口中显示localHost属性rn public string LocalHostrn rn get return localHost; rn set localHost = value; rn rnrn private int localPort = 11000;rn [Browsable(true), Category("Local"), Description("本地端口号")] //在“属性”窗口中显示localPort属性rn public int LocalPortrn rn get return localPort; rn set localPort = value; rn rnrn private bool active = false;rn [Browsable(true), Category("Local"), Description("激活监听")] //在“属性”窗口中显示active属性rn public bool Activern rn get return active; rn set //该属性读取值rn rn active = value;rn if (active) //当值为True时rn rn OpenSocket(); //打开监听rn rn elsern rn CloseSocket(); //关闭监听rn rn rn rnrnrn public UDPSocket()rn rn InitializeComponent();rn rnrn public UDPSocket(IContainer container)rn rn container.Add(this);rnrn InitializeComponent();rn rnrn protected void Listener() //监听rn rn ServerEndPoint = new IPEndPoint(IPAddress.Any,localPort); //将IP地址和端口号以网络端点存储rn if (UDP_Server != null)rn UDP_Server.Close();rnrn [color=#FF0000]UDP_Server = new UdpClient(localPort); //创建一个新的端口号[/color]rnrn UDP_Server.Client.ReceiveBufferSize = 1000000000; //接收缓冲区大小rn UDP_Server.Client.SendBufferSize = 1000000000; //发送缓冲区大小rn rn tryrn rn thdUdp = new Thread(new ThreadStart(GetUDPData)); //创建一个线程rn thdUdp.Start(); //执行当前线程rn rn catch (Exception e)rn rn MessageBox.Show(e.ToString()); //显示线程的错误信息rn rn rnrn private void GetUDPData() //获取当前接收的消息rn rn while (active)rn rn tryrn rn byte[] Data = UDP_Server.Receive(ref ServerEndPoint); //将获取的远程消息转换成二进制流rnrnrnrn if (DataArrival != null)rn rn DataArrival(Data, ServerEndPoint.Address, ServerEndPoint.Port);rn rn Thread.Sleep(0);rn rn catch rn rn rnrn private void CallBackMethod(IAsyncResult ar)rn rn //从异步状态ar.AsyncState中,获取委托对象rn DataArrivalEventHandler dn = (DataArrivalEventHandler)ar.AsyncState;rn //一定要EndInvoke,否则你的下场很惨rn dn.EndInvoke(ar);rn rnrnrn public void Send(System.Net.IPAddress Host, int Port, byte[] Data)rn rn tryrn rn IPEndPoint server = new IPEndPoint(Host, Port);rn UDP_Server.Send(Data, Data.Length, server);rn rn catch (Exception e)rn rn MessageBox.Show(e.ToString());rn rn rnrn private void OpenSocket()rn rn Listener();rn rnrn private void CloseSocket()rn rn if (UDP_Server != null)rn UDP_Server.Close();rn if (thdUdp != null)rn rn Thread.Sleep(30);rn thdUdp.Abort();rn rn rn rnrn[/code]rn 论坛

套接字地址问题

04-07

我用nc做服务端,自己创建个socket,用gethostbyname()函数获得一动态域名的地址,然后反向连接,rn一点反应也没有.rn#define MasterAddr "xxxxxxxxxxx.org" //连接地址rn#define MasterPort 5556 //连接端口rnstruct sockaddr_in local;rnint main() rn rn rn HOSTENT *host_entry;rn WSADATA WSA;rn rn if ((WSAStartup(MAKEWORD(2,2),&WSA)) != 0) rn printf("[e]Load WINSOCK Failed!\n");rn return -1;rn rn rn SOCKET sock_serv;rnrn local.sin_family = AF_INET;rn rn local.sin_port = htons(MasterPort);rn rn host_entry = gethostbyname(MasterAddr);rn/* printf("%s\n", MasterAddr);rn if(host_entry!=0)rn rn printf(" 解析IP地址: ");rn printf("%d.%d.%d.%d",rn (host_entry->h_addr_list[0][0]&0x00ff),rn (host_entry->h_addr_list[0][1]&0x00ff),rn (host_entry->h_addr_list[0][2]&0x00ff),rn (host_entry->h_addr_list[0][3]&0x00ff));rnrn */ rn memcpy( &local.sin_addr,host_entry->h_addr_list[0],host_entry->h_length ); rn rn rn sock_serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ;rn rn while (1)rn rn if (0 == connect(sock_serv, (struct sockaddr *)&local, sizeof(local))) rn open_door(sock_serv);rn else rn Sleep(10000);rn rn rn rnrn printf("success\n");rn closesocket(sock_serv);rn rn puts("Close Socket");rn WSACleanup();rn return 0;rnrnrnrn用本机做实验,local.sin_addr = inet_addr("127.0.0.1"),则可以,用ip地址60.128.XX.xx 也没反应rn.我的机器在一局域网,是不是这一问题导致无法工作,要改的话如何修改 论坛

没有更多推荐了,返回首页