( 2018年11月26日 )
目录
- 简略知识点
- 详解知识点
- 附录
- 客户机/服务器模式,套接字类型、面向TCP/UDP的socket编程 - [目标获得详解]
- Socket相关函数详解 - [目标获得详解]
- socket()函数 - int socket(int domain, int type, int protocol)
- bind函数 - int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen)
- sockaddr和sockaddr_in的区别
- in_addr_t inet_addr(const char* strptr);
- listen函数:int listen ( SOCKET s, int backlog )
- accept函数:SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR*addrlen )
- send函数:int send ( SOCKET s, const char FAR * buf, int len, int flags)
- recv函数:int recv (SOCKET s, char FAR* buf, int len, int flags )
- connect函数:int connect (SOCKET s,const struct sockaddrFAR* name, int namelen );
简略知识点
本地进程间通信(IPC)方式
- 消息传递(管道、FIFO、消息队列)
- 同步(互斥量,条件变量,读写锁,文件和写记录锁,信号量)
- 共享内存(匿名的、具名的)
- 远程过程调用(Solaris门和Sun RPC)
网络进程中如何通信
网络层的ip地址可以唯一标识网络的进程,传输层的协议+端口可以唯一标识主机中的应用程序(进程)。
利用三元组(ip地址、协议、端口)就可以标识网络的进程,网络中的进程通信就可以利用这个标志与其他进程进行交互。
Socket基本操作
socket是“open - write/read - close”模式的一种实现,故socket也提供了这些操作对应的函数接口。
Socket简单通信
- 功能:服务器端和客户端,服务器端监听端口发来的请求,收到后向客户端发送一个Hello World,客户机负责发送消息并打印收到的Hello World.
- 服务器:建立socket,绑定socket和地址信息,开启监听,收到请求后发送数据。
- 客户端:建立socket,连接服务器端,接收并打印服务器给的数据。
vs2015基于UDP协议的简单通信例程
- UDP发送和接受数据分别使用sendto()和recvfrom()函数
- 在vs2015中关于绑定本地Ip地址的inet_addr()函数在c++11中和以前的版本有所区别,新旧函数替换,或定义不检查。
- UDT流程与UDP类似。
详解知识点
Socket简述
- socket是网络编程的基本API,应用层编程API,提供了TCP/IP四层模型的第三层传输层的TCP、UDP协议的数据传输方式。第二层网络层有IP协议,不可靠协议,TCP在它基础上提供了可靠传输。UDP仍然提供不可靠传输。
- 两个进程通信,可通过socket,不管两个进程在什么位置,只要它们主机都实现了TCP/IP协议栈。一个通信实体用网络地址(ip)+port标识。
- 端口:是一种抽象软件结构(包括一些数据结构和I/O缓冲区)。应用程序同通过系统调用与某端口建立连接后,传输层传给该端口的数据都被相应的进程所接收,相应进程发给传输层的数据都通过该端口输出。端口用一个整数型标识符标识,端口号。端口使用一个16位数字表示,范围0-65535,1024以下端口号保留预定义服务。
Socket编程 - TCP服务器端流程
- TCP传输模式是服务器先创建一个socket,然后把这个socket绑定到一个地址上。这时socket可以工作,然后向tcp/ip协议栈请求一个监听服务并创建一个服务队列来接受那些请求。
- 第一次握手:建立连接时,客户端发送syn包(syn = j)到服务器,并进入SYN_SENT状态,等待服务器确认; SYN - 同步序列编号
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack = j+1),同时自己也发送一个SYN包(syn = k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack = k+1),此包发送完毕,客户端和服务器进入ESTANLESHED(TCP连接成功)状态,完成三次握手。
Socket相关函数详解 - [附录详解]
- socket()、bind()、listen()、accept()、close() - 服务器
- socket()、connect()、close() - 客户
TCP相关函数
- read()、write()、read() - TCP服务器
- write()、read() - TCP客户
附录
客户机/服务器模式,套接字类型、面向TCP/UDP的socket编程 - [目标获得详解]
- 客户机/服务器模式 : 在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机/服务器模式。
- 套接字的类型:流式套接字(SOCK_STREAM)提供面向连接、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收,实际上是基于TCP协议实现的。数据报式套接字(SOCK_DGRAM)提供无连接服务,数据包以独立句形式发送,不提供无错保证,数据可能丢失或重复。并且接收顺序混乱。实际上是基于UDP协议实现。原始套接字(SOCK_RAW)
面向TCP/UDP的socket编程:
TCP服务器端:
a):创建套接字(socket)
b):将套接字绑定到一个本地地址和端口上(bind)
c):将套接字设为监听模式,准备接受客户请求(listen)
d):等待客户请求到来,当请求到来后,接受连接请求,返回一个新的对应于此次的连接的套接字(accept)
e):用返回的套接字和客户端进行通信(send/recv)
f):返回,等待另一客户请求
g):关闭套接字
TCP客户端
a):创建套接字(socket)
b):向服务器发出连接请求(connect)
c):和服务器进行通信(send/recv)
d):关闭套接字
UDP服务器端(服务器端即先启动的一端为接收端)
a):创建套接字
b):将套接字绑定到一个本地址和端口上
c):等待接受数据(recvfrom)
d):关闭套接字
UDP客户端(发送数据的一端为发送到,也称客户端)
a):创建套接字
b):向服务器发送数据(sendto)
c):关闭套接字
Socket相关函数详解 - [目标获得详解]
- WSAStartup是为了向操作系统说明,调用哪个库文件,让该库文件与当前的应用程序绑定,从而就可以调用该版本的socket的各种函数了。
- 头文件 header: Winsock2.h
库library: Ws2_32.lib - 参数:
wVersionRequested是Windows Sockets API提供的调用方可使用的最高版本号。高位字节指出副版本(修正)号,低位字节指明主版本号。
lpWSAData 是指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。
WSADATA数据类型:这个结构被用来存储 被WSAStartup函数调用后返回的 Windows Sockets数据。它包含Winsock.dll执行的数据。 - 功能:主要就是进行相应的socket库绑定。
- 使用:当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
- 例:假如一个程序要使用2.1版本的Socket,那么程序代码如下
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
socket()函数 - int socket(int domain, int type, int protocol)
-
函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
#include<sys/types.h> #include<sys/socket.h>
-
参数:
函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
-
例:建立一个流式套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
bind函数 - int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen)
- 命名socket。socket名称包含"协议, ip地址, 端口号"这三个要素, 而命名就是通过调用bind函数把socket与这三个要素绑定一起来。
- 第一个参数是待绑定的套接字,第二个参数是标识绑定在哪个“地方”,第三个参数是这个“地方”的占地大小。返回值表示绑定操作是否成功,0表示成功, -1表示不成功。
- 例:调用
iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 注意强制转换
sockaddr和sockaddr_in的区别
#include <netinet/in.h>
- sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
程序员不应操作sockaddr,sockaddr是给操作系统用的
程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。 - 一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数
网络编程中一段典型的代码为:
int sockfd;
struct sockaddr_in servaddr;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
/* 填充struct sockaddr_in */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
/* 强制转换成struct sockaddr */
connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
in_addr_t inet_addr(const char* strptr);
返回:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
struct in_addr{
in_addr_t s_addr;
}
所处头文件: #include <arpa/inet.h>
- 例:
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本地回路地址
listen函数:int listen ( SOCKET s, int backlog )
- listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态
- 头文件:#include <sys/types.h> #include <sys/socket.h>
- 功能:listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。 - 参数:sockfd 一个已绑定未被连接的套接字描述符
backlog 连接请求队列(queue of pending connections)的最大长度。用SOMAXCONN则由系统确定。 - 例:isten(sockSrv, 5);
accept函数:SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR*addrlen )
- 函数的第一个参数用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字),第二个参数是用来保存客户端套接字对应的“地方”(包括客户端IP和端口信息等), 第三个参数是“地方”的占地大小。返回值对应客户端套接字标识。
- 例:
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
send函数:int send ( SOCKET s, const char FAR * buf, int len, int flags)
- send()是一个计算机函数,功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
- 向一个已连接的套接口发送数据。
#include <winsock.h>
int PASCAL FAR send( SOCKET s, const char FAR* buf, int len, int flags);
s:一个用于标识已连接套接口的描述字。
buf:包含待发送数据的缓冲区。
len:缓冲区中数据的长度。
flags:调用执行方式。
linux下函数:
intsend(SOCKETs, constcharFAR*buf, intlen, intflags); - 例:
send(sockConn, "test", 20, 0);//用返回的套接字和客户端进行通信。
recv函数:int recv (SOCKET s, char FAR* buf, int len, int flags )
- 实例:
char sendBuf[256];
char recvBuf[256];
recv(sockConn, recvBuf, strlen(recvBuf), 0);
Sleep(1000);
if (strlen(recvBuf) > 0)
{
cout << recvBuf << endl;
cout << "Input:";
cin >> sendBuf;
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);//用返回的套接字和客户端进
// 行通信。
}
connect函数:int connect (SOCKET s,const struct sockaddrFAR* name, int namelen );
- 实例:
SOCKADDR_IN addrSrv;//设定服务器端的IP和端口
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本地回路地址
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
//将其第三个参数设为0,让其 //自动选择协议。
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//与服务器建立连接。
Sleep(2000);
......
...
closesocket(sockClient);
...