`
tomotoboy
  • 浏览: 166813 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Berkeley套接字的一些基本知识

阅读更多
一、首先看一下Berkeley Socket实现TCP和UDP协议的流程
1.面向连接的TCP


2.无连接的UDP


二、Berkeley套接字的一些基本知识
1.基本结构
1.1、struct sockaddr
struct sockaddr{
unsigned short sa_family;
char sa_data[14];
};

sa_family 一般来说,都是“AFINET”。
sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。为了处理 struct sockaddr,  程序员建立了另外一个相似的结构struct sockaddr_in,struct sockaddr_in (“in” 代表 “Internet”)。

struct sockaddr_in {
short int sin_family;  		/* Internet地址族 */
unsigned short int sin_port;  	/*  端口号 */
struct in_addr sin_addr;  	/* Internet地址 */
unsigned char sin_zero[8]; 	/*  添0(和struct sockaddr一样大小)*/
};


1.2、struct in_addr
struct in_addr{
unsigned long s_addr;
};


2.基本转换函数
2.1.网络字节顺序
……
2.2.有关的转换函数
  • htons()——“Host to Network Short”    主机字节顺序转换为网络字节顺序(对无符号短型进行操作4 bytes)
  • htonl()——“Host to Network Long”   主机字节顺序转换为网络字节顺序(对无符号长型进行操作 8 bytes)
  • ntohs()——“ Network to Host Short”   网络字节顺序转换为主机字节顺序(对无符号短型进行操作 4 bytes)
  • ntohl()——“ Network to Host Long ”  网络字节顺序转换为主机字节顺序(对无符号长型进行操作 8 bytes)


在 struct sockaddr_in 中的 sin_addr 和 sin_port 他们的字节顺序都是网络字节顺序,而sin_family

却不是网络字节顺序的。为什么呢?
这个是因为 sin_addr 和sin_port 是从IP 和 UDP 协议层取出来的数据,而在IP和 UDP协议层,是直接和网络相关的,所以,它们必须使用网络字节顺序。然而, sin_family 域只是内核用来判断 struct sockaddr_in 是存储的什么类型的数据,并且,sin_family 永远也不会被发送到网络上,所以可以使用主机字节顺序来存储。

2.3.IP 地址转换
很幸运, Linux 系统提供和很多用于转换 IP 地址的函数,使你不必自己再写出一段费力不讨好的子程序来吃力的变换 IP。首先,让我假设你有一个 struct sockaddr_in ina,并且你的 IP 是 166.111.69.52  ,你想把你的 IP 存储到 ina 中。你可以使用的函数:inet_addr()  ,它能够把一个用数字和点表示 IP地址的字符串转换成一个无符号长整型。
你可以像下面这样使用它:
ina.sin_addr.s_addr = inet_addr(“ 166.111.69.52” );
注意:inet_addr()  返回的地址已经是网络字节顺序了,你没有必要再去调用 htonl()函数。
  • inet_ntoa() 使用struct in_addr作为一个参数,不是一个长整型值。
  • inet_ntoa() 返回一个字符指针,它指向一个定义在函数 inet_ntoa() 中的 static 类型字符串.


3.基本套接字调用
Linux 支持伯克利(BSD)风格的套接字编程.它同时支持面向连接和不连接类型的套接字。在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接.再不连接通讯中数据被作为信息的一部分被交换.无论那一种方式,服务器总是最先启动,把自己绑定(Banding)在一个套接字上,然后侦听信息.服务器究竟怎样试图去侦听就得依靠你编程所设定的连接的类型了。
3.1、socket() 函数
#include <sys/type.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain 需要被设置为 “AF_INET” ,就像上面的 struct sockaddr_in。然后,type参数告诉内核这个

socket 是什么类型, “SOCK_STREAM”或是“SOCK_DGRAM” 。最后,只需要把 protocol设置为 0 。
注意:事实上,domain 参数可以取除了“AF_INET”外的很多值,types 参数也可以取除了

“SOCK_STREAM”或“SOCK_DGRAM”的另外类型。具体可以参考socket 的man pages(帮助页)。

3.2bind()函数
#include <sys/types.h>
#include <sys/socket.h>
int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;
参数说明:
  • sockfd  是由socket()函数返回的套接字描述符。
  • my_addr 是一个指向 struct sockaddr 的指针,包含有关你的地址的信息:名称端口和 IP地址。
  • addrlen 可以设置为 sizeof(struct sockaddr)。


好,下面我们看一段程序:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 4000
main()
{
	int sockfd ;
	struct sockaddr_in my_addr ;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);       /*  在你自己的程序中要进行错误检查!

! */
	my_addr.sin_family = AF_INET ;  		/*  主机字节顺序 */
	my_addr.sin_port = htons(MYPORT);             /*  网络字节顺序,短整型 */
	my_addr.sin_addr.s_addr = inet_addr(“ 166.111.69.52”) ;
	bzero(&(my_addr.sin_zero), 8);                  /*  将整个结构剩余部分数据设为0 */

	/*  不要忘记在你自己的程序中加入判断 bind 错误的代码!! */
	bind (sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
	……
}


最后注意有关 bind()的是:有时候你并不一定要调用 bind()来建立网络连接。比如你只是想连接到一个远程主机上面进行通讯,你并不在乎你究竟是用的自己机器上的哪个端口进行通讯(比如 Telnet) ,那么你可以简单的直接调用 connect()函数,connect()将自动寻找出本地机器上的一个未使用的端口,然后调用 bind()来将其 socket 绑定到那个端口上。

3.3 connect()函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
  • sockfd     套接字文件描述符,由socket()函数返回的。
  • serv_addr  是一个存储远程计算机的IP地址和端口信息的结构。
  • addrlen    应该是 sizeof(struct sockaddr)。


下面让我们来看看下面的程序片段:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

#define DEST_IP "166,111,69.52"
#define DEST_PROT 23

main()
{
	int sockfd;
	struct sockaddr_in dest_addr;
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	dest_addr.sin_family=AF_INET;
	dest_addr.sin_port=htons(DEST_PORT);
	dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
	bzreo(&(dest_addr.sin_zero),8);
	
	/*  不要忘记在你的代码中对connect()进行错误检查!! */
	connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
	……

}
再次强调,一定要检测 connect()的返回值:如果发生了错误(比如无法连接到远程主机,或是远程主机的指定端口无法进行连接等)它将会返回错误值-1。 全局变量 errno将会存储错误代码。另外,注意我们没有调用 bind()函数。基本上,我们并不在乎我们本地用什么端口来通讯,是不是?我们在乎的是我们连到哪台主机上的哪个端口上。Linux 内核自动为我们选择了一个没有被使用的本地端口。

3.4 listen()函数
listen()函数是等待别人连接,进行系统侦听请求的函数。当有人连接你的时候,你两步需要做:通过 listen()函数等待连接请求,然后使用 accept()函数来处理。
listen()函数调用是非常简单的。函数声明如下:
#include <sys/socket.h>
int listen(int sockfd, int backlog);


listen()函数的参数意义如下:
  • sockfd  是一个套接字描述符,由socket()系统调用获得。
  • backlog 是未经过处理的连接请求队列可以容纳的最大数目。


如果你想在一个端口上接受外来的连接请求的话,那么函数的调用顺序为:
socket() ;
bind() ;
listen() ;
/*  在这里调用 accept()函数 */
……
下面将不给出例程,因为 listen()是非常容易理解的。下面的 accept()函数说明中的例程中,有 listen()的使用。

3.5、accept()函数
下面是 accept()函数的声明:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);


accept()函数的参数意义如下:
  • sockfd 是正在 listen()  的一个套接字描述符。
  • addr   一般是一个指向 struct sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的 IP 地址和端口)。
addrlen是一个本地的整型数值,在它的地址传给 accept()前它的值应该是sizeof(structsockaddr_in);accept()不会在 addr 中存储多余 addrlen bytes 大小的数据。如果accept()函数在addr 中存储的数据量不足 addrlen,则 accept()函数会改变 addrlen 的值来反应这个情况。
PS:如果调用 accept()失败的话,accept()函数会返回–1来表明调用失败,同时全局变量 errno 将会存储错误代码


#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
/*  用户连接的端口号 */
#define MYPORT 4000
#define BACKLOG 10
main()
{
        /* 用来监听网络连接的套接字sock_fd,用户连入的套接字使用new_fd*/
	int sockfd , new_fd;
	struct sockaddr_in my_addr;
	struct sockaddr_in their_addr;
	
	int sin_size;
	/*记得在自己的程序中这部分要进行错误检查!*/

	sockfd=socket(AF_INET,SOCK_STREAM, 0);
	my_addr.sin_family=AF_INET;
	my_addr.sin_port=htons(MYPORT);
	my_addr.sin_addr.s_addr=INADDR_ANY
	bzero(&my_addr.sin_zero),8);

	bind(sockfd,(struct sockaddr *)&my_addr,sizeof(sockaddr));
	listen(sockfd,BACKLOG);
	sin_size=sizeof(struct sockaddr_in);
	new_fd=accept(sockfd,&their_addr,&sin_size);
	……
	……
	
}


面向连接的通信中客户机要做如下一些事:
调用 socket()函数创建一个套接字。
调用 connect()函数试图连接服务。
如果连接成功调用 write()函数请求数据,调用 read()函数接收引入的应答。

3.6、send()、recv()函数
这两个函是最基本的
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void *msg, int len, int flags);

send 的参数含义如下:
  • sockfd  是代表你与远程程序连接的套接字描述符。
  • msg     是一个指针,指向你想发送的信息的地址。
  • len     是你想发送信息的长度。
  • flags   发送标记。一般都设为 0(你可以查看 send 的man pages 来获得其他的参数值并且明白各个参数所代表的含义)。


下面看看有关 send()函数的代码片段:
char *msg = “ Hello! World!”;
int len, bytes_sent;
……
……
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
……
……
……
send()函数在调用后会返回它真正发送数据的长度。
注意:send()  所发送的数据可能少于你给它的参数所指定的长度!
因为如果你给 send()的参数中包含的数据的长度远远大于 send()所能一次发送的数据,则 send()函数只发送它所能发送的最大数据长度,然后它相信你会把剩下的数据再次调用它来进行第二次发送。所以,记住如果send()函数的返回值小于 len 的话,则你需要再次发送剩下的数据。幸运的是,如果包足够小(小于1K) ,那么send()一般都会一次发送光的。

下面我们来看看 recv()函数。
函数 recv()调用在许多方面都和send()很相似,下面是recv()函数的声明:
#include <sys/types.h>
#include <sys/socket.h>
int recv(int sockfd, void *buf, int len, unsigned int flags);


recv()的参数含义如下:
  • sockfd 是你要读取数据的套接字描述符。
  • buf    是一个指针,指向你能存储数据的内存缓存区域。
  • len    是缓存区的最大尺寸。
  • flags  是 recv()  函数的一个标志, 一般都为0 (具体的其他数值和含义请参考 recv()的 man pages)。


recv()  返回它所真正收到的数据的长度。(也就是存到 buf 中数据的长度) 。如果返回– 1 则代表发生了错误(比如网络以外中断、对方关闭了套接字连接等) ,全局变量 errno 里面存储了错误代码。


3.7 、sendto() 和recvfrom() 函数
这两个函数是进行无连接的 UDP 通讯时使用的。使用这两个函数,则数据会在没有建立过任何连接的网络上传输。因为数据报套接字无法对远程主机进行连接,想想我们在发送数据前需要知道些什么呢?
PS:取得远程主机的 IP 地址和端口!

下面是 sendto()函数和 recvfrom()函数的声明:
#include <sys/types.h>
#include <sys/socket.h>
int sendto (int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr * to, int tolen);
  • sockfd 是代表你与远程程序连接的套接字描述符。
  • msg    是一个指针,指向你想发送的信息的地址。
  • len    是你想发送信息的长度。
  • flags  发送标记。一般都设为 0。 (你可以查看 send 的 man pages 来获得其他的参数值并且明白各个参数所代表的含义)
  • to     是一个指向struct sockaddr 结构的指针,里面包含了远程主机的IP 地址和端口
  • 数据。
  • tolen  只是指出了 struct sockaddr 在内存中的大小sizeof(struct sockaddr)。

和 send()一样,sendto()返回它所真正发送的字节数(当然也和 send()一样,它所真正发送的字节数可能小于你所给它的数据的字节数)。 当它发生错误的时候,也是返回–1 ,同时全局变量 errno 存储了错误代码。


recvfrom()的声明为:
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr * from, int 

* fronlen)

  • sockfd  是你要读取数据的套接字描述符。
  • buf     是一个指针,指向你能存储数据的内存缓存区域。
  • len     是缓存区的最大尺寸。
  • flags   是 recv()  函数的一个标志,一般都为0具体的其他数值和含义请参考 recv()的man pages。
  • from    是一个本地指针,指向一个struct sockaddr的结构(里面存有源IP 地址和端口数).
  • fromlen 是一个指向一个 int 型数据的指针,它的大小应该是 sizeof(struct sockaddr) .当函数返回的时候,formlen 指向的数据是 form 指向的 struct
  • sockaddr 的实际大小.


recvfrom()  返回它接收到的字节数,如果发生了错误,它就返回– 1  ,全局变量 errno存储了错误代码.如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉。该调用可以立即返回,也可以永久的等待。这取决于你把 flags 设置成什么类型。你甚至可以设置超时(timeout)值。在说明书(man pages)中可以找到recvfrom的更多信息。


注意:如果你使用 cnnect()连接到了一个数据报套接字的服务器程序上,那么你就可以使用 send()  和 recv()  函数来传输你的数据.不要以为你在使用一个流式的套接字,你所使用的仍然是一个使用者数据报的套接字,只不过套接字界面在 send()  和 recv()的时候自动帮助你加上了目标地址,目标端口的信息.



  • 大小: 20.9 KB
  • 大小: 18.2 KB
分享到:
评论

相关推荐

    windows原是套接字

    在IT行业中,Windows原生套接字(Native Windows Sockets)是Microsoft在其Windows操作系统中实现的一种网络通信机制,它允许应用程序通过使用标准的Berkeley套接字API来访问网络服务。"windows原是套接字"这个标题...

    tcp套接字例子windos

    TCP套接字API在Windows上通常是通过伯克利套接字接口(Berkeley Sockets API)实现的,它提供了丰富的功能来创建、连接、读写以及管理网络连接。下面我们将深入探讨TCP套接字在Windows环境中的应用,并基于`select`...

    Winsock 教程_套接字试验

    Winsock是Windows操作系统中的网络编程接口,它提供了标准的Berkeley套接字API,使得程序员可以编写跨平台的网络应用程序。本教程旨在为初学者提供一个全面的Winsock学习框架,通过实践性的套接字试验,深入理解网络...

    基于流式套接字的并发服务器的应用程序

    在C语言中,我们可以使用标准的Berkeley套接字API来实现基于流式套接字的并发服务器。下面是一些关键知识点: 1. **套接字创建**:首先,使用`socket()`函数创建一个套接字描述符,指定协议族(通常是PF_INET,代表...

    简单的套接字编程,讲的很易懂

    ### 套接字编程基础知识点详解 #### 1.1 什么是套接字 套接字(Socket),作为同一台主机内部应用层与传输层之间的接口,扮演着应用程序和网络之间的桥梁角色,即应用程序接口(API)。它允许应用程序通过网络进行...

    套接字聊天程序CSocketDemo

    在C++中,通常使用Windows Socket API(Winsock)或Berkeley套接字来实现。CSocketDemo很可能基于这些API来构建它的网络通信机制。 ### 2. 客户端与服务器 在聊天程序中,通常有两种角色:客户端(Client)和...

    原始套接字实例:MAC 头部报文分析

    在Linux网络编程中,原始套接字(Raw Socket)是一种特殊类型的套接字,它允许程序员访问网络协议栈的底层,直接操作数据包而不依赖于操作系统提供的高层协议处理。通过原始套接字,我们可以捕获并分析网络上的...

    C++套接字实现UDP通讯,客户端以及服务端demo

    总结,本文介绍了C++使用Berkeley套接字实现UDP通信的基本原理和步骤,涵盖了客户端和服务端的核心功能。实际开发中,可以根据需求进行扩展,如添加多线程处理、数据包序列化与反序列化、错误处理等功能。通过深入...

    网络应用程序设计-基于TCP套接字的编程

    本章深入讲解了如何利用TCP套接字进行通信,包括基本概念、常用函数以及实际编程示例。 2.1 概述 TCP套接字编程起源于20世纪80年代的Berkeley UNIX(BSD UNIX),当时是为了简化应用进程与网络之间的通信而设计的...

    VC服务器与客户端套接字通信

    Winsock提供了一组与Berkeley套接字兼容的函数,使得开发者能够方便地在VC环境中进行网络编程。 二、VC套接字服务器 服务器端的主要任务是监听特定端口,等待客户端的连接请求。在VC中,创建服务器套接字的步骤包括...

    server-and-client.rar_server client_套接字

    在VC++中,实现套接字通信通常需要包含Winsock库,这是Microsoft提供的一个API,兼容Berkeley套接字接口。开发者需要初始化Winsock,创建套接字,绑定到特定端口,监听客户端连接,接受连接,发送和接收数据,最后...

    C语言套接字聊天程序.zip

    在Linux系统中,网络编程主要依赖于POSIX标准和Berkeley套接字API。Linux提供了丰富的系统调用来支持网络编程,如`socket()`, `bind()`, `listen()`, `accept()`, `connect()`, `send()`, `recv()`, `close()`等。...

    BSD套接字PDF 高清

    ### BSD套接字知识点概述 #### 一、BSD套接字简介 - **定义与起源**:BSD(Berkeley Software Distribution)套接字是一种用于进程间通信(Inter-Process Communication, IPC)的设计模式,主要用于实现计算机间的...

    实验27网络通信-套接字.zip

    在Linux系统中,套接字API遵循Berkeley套接字模型,允许程序员创建和管理网络连接。 **2. TCP/IP协议** 实验中使用的通信机制基于TCP/IP协议族,它包含了传输层的TCP(Transmission Control Protocol)协议。TCP是...

    C++套接字编程,工具VS2019

    在C++中,我们通常使用伯克利套接字API(Berkeley Sockets API)来实现套接字编程。该API提供了一系列的函数接口,包括创建套接字、绑定套接字到特定端口、监听连接请求、接受连接和发送/接收数据等。 1. 创建套接...

    BSD套接字API资料锦集

    **BSD套接字API**是计算机网络编程中的一个重要概念,源于Berkeley Software Distribution(BSD)系统,后来被广泛应用于各种操作系统,包括Unix、Linux以及Windows等。它为开发者提供了一组标准化的接口,用于实现...

    VC套接字编程(2)[归纳].pdf

    它遵循Berkeley套接字接口,允许开发者创建网络应用。 2. **服务器端编程**: - 创建对话框工程:首先,我们需要创建一个基于对话框的MFC工程,名为`SockServ`,这将作为服务器端的应用程序。 - 添加套接字变量:...

    berkeley-socket.rar_ Berkeley Socket _berkeley socket

    2. Socket类型:主要分为流式套接字(SOCK_STREAM,对应TCP)和数据报套接字(SOCK_DGRAM,对应UDP)。流式套接字提供面向连接、可靠的字节流服务;数据报套接字则无连接,且数据包可能会丢失或重复,但传输效率高。...

    套接字学习

    ### 套接字学习知识点总结 #### 一、网络概述 **1.1 何为网络** 网络是指将地理位置不同、具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,按照网络协议进行数据通信,实现资源共享的信息系统。 *...

    Linux系统中基于TCP_IP套接字中间件的设计与实现.pdf

    套接字API(Application Programming Interface)源自Berkeley的BSD UNIX系统,已经成为事实上的工业标准接口,被大多数操作系统所支持,用于编写各种网络应用程序。 Linux套接字是一种通用的网络编程接口,它允许...

Global site tag (gtag.js) - Google Analytics