TCP是可靠地,如果这个通道坏了,会有其他方法修复或重建通道,消息太多时也会有办法去处理溢出的信息。
UDP来讲,设计角度就是不可靠的,省略其中很多流程,得到较快的速度,数据的可靠性由上层协议即利用层来保证,消息是不是转达也没法做出保证。
TCP/IP模型里面,网络分为5层:从上往下是利用层,传输层,网络层,数据链路层,物理层。
*物理层:通讯的物理条件。
*数据链路层:队伍里信号的第1次处理,保证这些数字信号能够可靠的发送到目标的网络层。
接收端保证数据的可靠性,校验算法,对数据进行分组,将IP地址转换成MAC地址(48位的2进制串,1台机器的物理地址)
*网络层:对数据从传输的速率优化算法和协议,主要作用是转发和选路,路由器,IP协议在这1层。
*传输层:两个最主要的协议就是TCP协议和UDP协议。
*利用层:最多见的协议是HTTP(基于SSL的协议,安全性较高),DNS(域名系统)在此层。(程序员工作的部份,socket工作在此层)
TCP是可靠的,由于TCP协议要做流量控制,保证数据传输的可靠性,连接收理,堵塞控制等,而这些UDP都没有,但是优点是比较快,对需要快并且信息不重要的场合如qq比较适合。
在发出1个功能调用时,在没有得到结果之前,该调用就不返回。依照这个定义,其实绝大多数函数都是同步调用。但是1般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或需要1定时间完成的任务。最多见的例子就是SendMessage。该函数发送1个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理终了以后,该函数才把消息处理函数所返回值返回给调用者。
异步的概念和同步相对。当1个异步进程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。当1个客户端通过调用 Connect函数发出1个连接要求后,调用者线程立刻可以向下运行。当连接真正建立起来以后,socket底 层会发送1个消息通知该对象。
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果以后才会返回。看到这里或许会把阻塞调用和同步调用同等起来,实际上它们是不同的。对同步调用来讲,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用Receive函数,如果缓冲区中没有数据,这个函数就会1直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。socket接收数据的另外1个函数recv则是1个阻塞调用的例子。当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。对象的阻塞模式和阻塞函数调用对象是不是处于阻塞模式和函数是否是阻塞调用有很强的相干性,但是其实不是1 1对应的。
阻塞就是干不完不准回来,1直处于等待中,直到事情处理完成才返回;
非阻塞就是你先干,我先看看有其他事没有,1发现事情被卡住,马上报告领导。
阻塞对象上可以有非阻塞的调用方式,我们可以通过1定的API去轮询状态,在适当的时候调用阻塞函数,就能够避免阻塞。而对非阻塞对象,调用特殊的函数也能够进入阻塞调用。函数select就是这样的1个例子。
Socket是进程通讯的1种方式,即调用这个网络库的1些API函数。Socket可以理解为客户端或服务器真个1个特殊的对象,这个对象有两个关键的方法,1个是getInputStream方法,另外一个是getOutputStream方法。getInputStream方法可以得到1个输入流,客户真个Socket对象上的getInputStream方法得到的输入流其实就是从服务器端发回的数据流。GetOutputStream方法得到1个输出流,客户端Socket对象上的getOutputStream方法返回的输出流就是将要发送到服务器真个数据流客户基于服务器之间使用的大部份通讯组件都是基于socket接口来实现的。
1、 首先调用Socket类的构造函数,以服务器的指定的IP地址或指定的主机名和指定的端口号为参数,创建1个Socket流,在创建Socket流的进程中包括了向服务器要求建立通讯连接的进程实现。
2、 建立了客户端通讯Socket后。就能够使用Socket的方法getInputStream()和getOutputStream()来创建输入/输出流。这样,使用Socket类后,网络输入输出也转化为使用流对象的进程。
3、 使用输入输出流对象的相应方法读写字节流数据,由于留连接着通讯所用的Socket,Socket又是和服务器端建立连接的1个端点,因此数据将通过连接从服务器得到或发向服务器。这时候我们就能够对字节流数据按客户端和服务器之间的协议进行处理,完成双方的通讯任务。
4、 待通讯任务终了后,我们用流对象的close()方法来关闭用于网络通讯的输入输出流,在用Socket对象的close()方法来关闭Socket。
服务器必须首先启动,直到它履行完accept()调用,进入等待状态后,方能接收客户要求。假设客户在此前启动,则connect()将返回出错代码,连接不成功。
###UDP
无连接服务器也必须先启动,否则客户要求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间还没有建立完全相干,但各自通过socket()和bind()建立了半相干。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发进程中动态地建立了全相干。
先说明头文件# include < WinSock2.h >(windows下)
基本学习的函数以下:socket、bind、listen、connect、accept、send、recv、recvfrom、close、shutdown
int socket(int domain,int type,int protocol)
非负为成功,⑴为失败。
1.指定通讯产生的区域 (Windows下仅支持AF_INET)
2.套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;
3.protocol1般取为0。成功时,返回1个小的非负整数值,与文件描写符类似.
eg:
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror(“opening stream socket”);
exit(1);
}
Int bind( IN SOCKET s, IN const struct sockaddr FAR * name, IN int namelen);
// IN windows的宏,表示输入
当socket函数返回1个描写符时,只是存在于其协议族的空间中,并没有分配1个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将1组固定的地址绑定到sockfd上。
1.是socket函数返回的描写符;
2.指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
3.是前面struct sockaddr(与sockaddr_in等价)的长度。
为了统1地址结构的表示方法,统1接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但1般的编程中其实不直接对此数据结构进行操作,而使用另外一个与之等价的数据结构sockaddr_in。
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
{
perror(“binding stream socket”);
exit(1);
}
int listen(int sockfd,int backlog)
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用
0成功,⑴失败
1、1个本地已建立、还没有连接的套接字号,服务器愿意从它上面接收要求
2、表示要求连接队列的最大长度,用于限制排队要求的个数,目前允许的最大值为5
if (listen(initsockid , 5) < 0)
error(“listen error”);
这两个系统调用用于完成1个完全相干的建立,其中connect()用于建立连接。无连接的套接字进程也能够调用connect(),但这时候在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员没必要为每数据指定目的地址,而且如果收到的1个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端便能判断该端口不可操作。
int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)
0成功,⑴失败
1.是欲建立连接的本地套接字描写符。
2.指出说明对方套接字地址结构的指针
3.对方套接字地址长度由namelen说明
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
非负数为成功,⑴失败
1.为本地套接字描写符,在用做accept()调用的参数前应当先调用过listen()
2.指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。
3.为客户方套接字地址的长度(字节数)。
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
成功拷贝至发送缓冲区的字节数(可能小于len),⑴出错,并置毛病号errno.
1、为已连接的本地套接字描写符
2、指向存有发送数据的缓冲区的指针
3、buf长度由len 指定
4、指定传输控制方式,如是不是发送带外数据等
int recv(int sockfd,void *buf, size_t len,int flags)
成功时,返回拷贝的字节数,失败返回⑴
1、为已连接的套接字描写符
2、指向接收输入数据缓冲区的指针
3、buf长度由len 指定
4、指定传输控制方式,如是不是接收带外数据等
迷之类似,只有进程的发出者相对不同
// server.cpp
#include <iostream>
#include <cstdio>
#include <Winsock2.h>
using namespace std;
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // 要求1.1版本的WinSock库
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1; // 返回值为零的时候是表示成功申请WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
// 检查这个低字节是否是1,高字节是否是1以肯定是不是我们所要求的1.1版本
// 否则的话,调用WSACleanup()清除信息,结束函数
WSACleanup( );
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockSrv
// SOCKET socket(int af, int type, int protocol);
// 第1个参数,指定地址簇(TCP/IP只能是AF_INET,也可写成PF_INET)
// 第2个,选择套接字的类型(流式套接字),第3个,特定地址家族相干协议(0为自动)
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 套接字sockSrv与本地地址相连
// int bind(SOCKET s, const struct sockaddr* name, int namelen);
// 第1个参数,指定需要绑定的套接字;
// 第2个参数,指定该套接字的本地地址信息,该地址结构会随所用的网络协议的不同而不同
// 第3个参数,指定该网络协议地址的长度
// PS: struct sockaddr{ u_short sa_family; char sa_data[14];};
// sa_family指定该地址家族, sa_data起到占位占用1块内存分配区的作用
// 在TCP/IP中,可以使用sockaddr_in结构替换sockaddr,以方便填写地址信息
//
// struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];};
// sin_family表示地址族,对IP地址,sin_family成员将1直是AF_INET。
// sin_port指定将要分配给套接字的端口。
// sin_addr给出套接字的主机IP地址。
// sin_zero[8]给出填充数,让sockaddr_in与sockaddr结构的长度1样。
// 将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。
// 如果想只让套接字使用多个IP中的1个地址,可指定实际地址,用inet_addr()函数。
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 将INADDR_ANY转换为网络字节序,调用 htonl(long型)或htons(整型)
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 第2参数要强迫类型转换
// 将套接字设置为监听模式(连接要求), listen()通知TCP服务器准备好接收连接
// int listen(SOCKET s, int backlog);
// 第1个参数指定需要设置的套接字,第2个参数为(等待连接队列的最大长度)
listen(sockSrv, 10);
// accept(),接收连接,等待客户端连接
// SOCKET accept( SOCKET s, struct sockaddr* addr, int* addrlen);
// 第1个参数,接收1个处于监听状态下的套接字
// 第2个参数,sockaddr用于保存客户端地址的信息
// 第3个参数,用于指定这个地址的长度
// 返回的是向与这个监听状态下的套接字通讯的套接字
// 客户端与用户端进行通讯
// send(), 在套接字上发送数据
// int send( SOCKET s, const char* buf, int len, int flags);
// 第1个参数,需要发送信息的套接字,
// 第2个参数,包括了需要被传送的数据,
// 第3个参数是buffer的数据长度,
// 第4个参数,1些传送参数的设置
// recv(), 在套接字上接收数据
// int recv( SOCKET s, char* buf, int len, int flags);
// 第1个参数,建立连接后的套接字,
// 第2个参数,接收数据
// 第3个参数,接收数据的长度,
// 第4个参数,1些传送参数的设置
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while(true){ // 不断等待客户端要求的到来
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[100];
sprintf(sendBuf, "Welcome %s to the server program~ \nNow, let's start talking...\n", inet_ntoa(addrClient.sin_addr));
send(sockConn, sendBuf, strlen(sendBuf)+1, 0); // 发送显示欢迎信息
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0);
printf("%s\n", recvBuf); // 接收第1次信息
char * sockConnName = "Client";
printf("我们可以聊5句话");
int n = 5;
while(n--){
printf("还剩%d次:\n", n+1);
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0);
printf("%s Says: %s\n", sockConnName, recvBuf); // 接收信息
char talk[100];
printf("Please enter what you want to say next(\"quit\"to exit):");
gets(talk);
send(sockConn, talk, strlen(talk)+1, 0); // 发送信息
printf("\n");
}
printf("\nEnd talking... \n");
closesocket(sockConn);
}
printf("\n");
system("pause");
return 0;
}
#include <iostream>
#include <cstdio>
#include <Winsock2.h>
using namespace std;
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // 要求1.1版本的WinSock库
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1; // 返回值为零的时候是表示成功申请WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
// 检查这个低字节是否是1,高字节是否是1以肯定是不是我们所要求的1.1版本
// 否则的话,调用WSACleanup()清除信息,结束函数
WSACleanup( );
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
// SOCKET socket(int af, int type, int protocol);
// 第1个参数,指定地址簇(TCP/IP只能是AF_INET,也可写成PF_INET)
// 第2个,选择套接字的类型(流式套接字),第3个,特定地址家族相干协议(0为自动)
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第1个参数:需要进行连接操作的套接字
// 第2个参数:设定所需要连接的地址信息
// 第3个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s\n", recvBuf);
send(sockClient, "Attention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);
printf("我们可以聊5句话");
int n = 5;
do{
printf("\n还剩%d次:", n);
char talk[100];
printf("\nPlease enter what you want to say next(\"quit\"to exit):");
gets(talk);
send(sockClient, talk, strlen(talk)+1, 0); // 发送信息
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s Says: %s\n", "Server", recvBuf); // 接收信息
}while(--n);
printf("End linking...\n");
closesocket(sockClient);
WSACleanup(); // 终止对套接字库的使用
printf("\n");
system("pause");
return 0;
}
下一篇 你会写“atoi”吗???