socket编程函数,socket套接字编程
大家好,关于socket编程函数很多朋友都还不太明白,今天小编就来为大家分享关于socket套接字编程的知识,希望对各位有所帮助!
QT 编程QTcpSocket 类write函数
if(m_httpAddress.isEmpty()|| sendBuffer.isEmpty())
{
return 0;
}//服务器地址或者需要发送的数据为空直接返回
QTcpSocket socket;
socket.connectToHost(m_httpAddress, m_httpPort);//建立一个TCP连接,主机地址是m_httpAddress,端口号是httpPort
socket.setSocketOption(QAbstractSocket::LowDelayOption, 1);//优化为最低延迟,后面的1代码启用该优化。
if(!socket.waitForConnected())
{
return 0;
}
//等待连接,如果超过3s没有客户端连接将退出。
socket.write(sendBuffer);//开始传输数据
socket.waitForBytesWritten();
while(socket.waitForReadyRead(60000))//在6s内完成数据的传输
{
while(socket.bytesAvailable())
{
receiveBuffer+= socket.readAll();
}//如果传输数据不为0,那么接受数据buffer加上该值
}
socket.close();//关闭I/O数据传输以及Tcp连接,并重置主机名和端口号
Socket编程的几种模式
其基本原理是:首先建立一个socket连接,然后对其进行操作,比如,从该socket读数据。因为网络传输是要一定的时间的,即使网络通畅的情况下,接受数据的操作也要花费时间。对于一个简单的单线程程序,接收数据的过程是无法处理其他操作的。比如一个窗口程序,当你接收数据时,点击按钮或关闭窗口操作都不会有效。它的缺点显而易见,一个线程你只能处理一个 socket,用来教课还行,实际使用效果就不行了。select模型为了处理多个socket连接,聪明的人们发明了select模型。该模型以集合来管理socket连接,每次去查询集合中的socket状态,从而达到处理多连接的能力,其函数原型是int select(int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout)。比如我们判断某个socket是否有数据可读,我们首先将一个fdread集合置空,然后将socket加入到该集合,调用 select(0,&fdread,NULL,NULL,NULL),之后我们判断socket是否还在fdread中,如果还在,则说明有数据可读。数据的读取和阻塞模型相同,调用recv函数。但是每个集合容量都有一个限值,默认情况下是64个,当然你可以重新定义它的大小,但还是有一个最上限,自己设置也不能超过该值,一般情况下是1024。尽管select模型可以处理多连接,但集合的管理多少让人感到繁琐。异步选择模型熟悉windows操作系统的都知道,其窗口处理是基于消息的。人们又发明了一种新的网络模型——WSAAsyncSelect模型,即异步选择模型。该模型为每个socket绑定一个消息,当socket上出现事先设置的socket事件时,操作系统就会给应用程序发送这个消息,从而对该 socket事件进行处理,其函数原型是int WSAAsynSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent)。hWnd指明接收消息的句柄,wMsg指定消息ID,lEvent按位设置感兴趣的网络事件,入 WSAAsyncSelect(s,hwnd,WM_SOCKET, FD_CONNECT| FD_READ| FD_CLOSE)。该模型的优点是在系统开销不大的情况下同时处理许多连接,也不需要什么集合管理。缺点很明显,即使你的程序不需要窗口,也要专门为 WSAAsyncSelect模型定义一个窗口。另外,让单个窗口去处理成千上万的socket操作事件,很可能成为性能瓶颈。事件选择模型与WSAAsynSelect模型类似,人们还发明了WSAEventSelect模型,即事件选择模型。看名字就可以猜测出来,它是基于事件的。WSAAsynSelect模型在出现感兴趣的socket事件时,系统会发一个相应的消息。而WSAEventSelect模型在出现感兴趣的socket事件时,系统会将相应WSAEVENT事件设为传信。可能你现在对sokect事件和普通WSAEVENT事件还不是很清楚。 socket事件是与socket操作相关的一些事件,如FD_READ,FD_WRITE,FD_ACCEPT等。而WSAEVENT事件是传统的事件,该事件有两种状态,传信(signaled)和未传信(non-signaled)。所谓传信,就是事件发生了,未传信就是还没有发生。我们每次建立一个连接,都为其绑定一个事件,等到该连接变化时,事件就会变为传信状态。那么,谁去接受这个事件变化呢?我们通过一个 WSAWaitForMultipleEvents(...)函数来等待事件发生,传入参数中的事件数组中,只有有一个事件发生,该函数就会返回(也可以设置为所有事件发生才返回,在这里没用),返回值为事件的数组序号,这样我们就知道了哪个事件发生了,也就是该事件对应的socket有了socket操作事件。该模型比起WSAAsynSelect模型的优势很明显,不需要窗口。唯一缺点是,该模型每次只能等待64个事件,这一限制使得在处理多 socket时,有必要组织一个线程池,伸缩性不如后面要讲的重叠模型。重叠I/O(Overlapped I/O)模型重叠I/O(Overlapped I/O)模型使应用程序达到更佳的系统性能。重叠模型的基本设计原理是让应用程序使用重叠数据结构,一次投递一个或多个Winsock I/O请求。重叠模型到底是什么东西呢?可以与WSAEventSelect模型做类比(其实不恰当,后面再说),事件选择模型为每个socket连接绑定了一个事件,而重叠模型为每个socket连接绑定了一个重叠。当连接上发生socket事件时,对应的重叠就会被更新。其实重叠的高明之处在于,它在更新重叠的同时,还把网络数据传到了实现指定的缓存区中。我们知道,前面的网络模型都要用户自己通过recv函数来接受数据,这样就降低了效率。我们打个比方,WSAEventSelect模型就像邮局的包裹通知,用户收到通知后要自己去邮局取包裹。而重叠模型就像送货上门,邮递员发给你通知时,也把包裹放到了你事先指定的仓库中。重叠模型又分为事件通知和完成例程两种模式。在分析这两种模式之前,我们还是来看看重叠数据结构: typedef struct WSAOVERLAPPED{DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; WSAEVENT hEvent;}WSAOVERLAPPED, FAR* LPWSAOVERLAPPED;该数据结构中,Internal、InternalHigh、Offset、OffsetHigh都是系统使用的,用户不用去管,唯一关注的就是 hEvent。如果使用事件通知模式,那么hEvent就指向相应的事件句柄。如果是完成例程模式,hEvent设为NULL。我们现在来看事件通知模式,首先创建一个事件hEvent,并创建一个重叠结构AcceptOverlapped,并设置AcceptOverlapped.hEvent= hEvent,DataBuf是我们事先设置的数据缓存区。调用 WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,NULL),则将AcceptSocket与AcceptOverlapped重叠绑定在了一起。当接收到数据以后,hEvent就会设为传信,而数据就会放到 DataBuf中。我们再通过WSAWaitForMultipleEvents(...)接收到该事件通知。这里我们要注意,既然是基于事件通知的,那它就有一个事件处理上限,一般为64。完成例程和事件通知模式的区别在于,当相应的socket事件出现时,系统会调用用户事先指定的回调函数,而不是设置事件。其实就是将WSARecv的最后一个参数设为函数指针。该回调函数的原型如下: void CALLBACK CompletionROUTINE( DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);其中,cbTransferred表示传输的字节数,lpOverlapped是发生socket事件的重叠指针。我们调用 WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,WorkerRoutine)将AcceptSocket与WorkRoutine例程绑定。这里有一点小提示,当我们创建多个socket的连接时,最好把重叠与相应的数据缓存区用一个大的数据结构放到一块,这样,我们在例程中通过lpOverlapped指针就可以直接找到相应的数据缓存区。这里要注意,不能将多个重叠使用同一个数据缓存区,这样在多个重叠都在处理时,就会出现数据混乱。完成端口模型下面我们来介绍专门用于处理为数众多socket连接的网络模型——完成端口。因为需要做出大量的工作以便将socket添加到一个完成端口,而其他方法的初始化步骤则省事多了,所以对新手来说,完成端口模型好像过于复杂了。然而,一旦弄明白是怎么回事,就会发现步骤其实并非那么复杂。所谓完成端口,实际是Windows采用的一种I/O构造机制,除套接字句柄之外,还可以接受其他东西。使用这种模式之前,首先要创建一个I/O完成端口对象,该函数定义如下: HANDLE CreateIoCompletionPort( HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads);该函数用于两个截然不同的目的:1)用于创建一个完成端口对象。2)将一个句柄同完成端口关联到一起。通过参数NumberOfConcurrentThreads,我们可以指定同时运行的线程数。理想状态下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程任务切换。对于一个socket连接,我们通过 CreateIoCompletionPort((HANDLE)Accept,CompletionPort,(DWORD)PerHandleData,0)将Accept连接与CompletionPort完成端口绑定到一起,CompetionPort对应的那些线程不断通过GetQueuedCompletionStatus来查询与其关联的socket连接是否有I/O操作完成,如果有,则做相应的数据处理,然后通过WSARecv将该socket连接再次投递,继续工作。完成端口在性能和伸缩性方面表现都很好,相关联的socket连接数目没有限制。
windows下socket如何编程
一个简单的TCP客户端程序流程
1、使用WSAStartup()初始化WinSock库。
2、使用socket()创建一个IPPROTO_TCP SOCKET。
3、使用gethostbyname()/gethostbyaddr()获取主机信息。
4、使用connect()和我们创建的套接字连接服务器。
5、使用send()/recv()发送和接收数据,直到我们的TCP会话结束。
6、使用closesocket()关闭套接字连接。
7、使用WSACleanup()释放WinSock。
初始化WinSock
正如其它每个WinSock程序一样,我们需要初始化WinSock库。这也基本上是一种检查WinSock是否在当前系统可用的方法,对于以前的版本,我们当然希望是这样。
int wsaret=WSAStartup(0x101,&wsaData);
if(wsaret)
return;
创建SOCKET
套接字是一种实体,它担当了客户端和服务器之间的端点。当客户端连接到服务器之后,就会存在两个套接字——客户端一边的套接字和相应的服务器一边的套接字。让我们来称它们为CLIENTSOCK和SERVERSOCK。当客户端在CLIENTSOCK使用send()时,服务器可以在SERVERSOCK使用recv()来接收客户端所发送的数据,反之亦然。对于我们的目的,我们使用一个名为socket()的函数来创建套接字。
SOCKET conn;
conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(conn==INVALID_SOCKET)
return;
获取主机信息
显然,我们在连接到主机(服务器)之前,要获取它的信息。我们可以使用两个函数——gethostbyname()和gethostbyaddr()。当我们拥有服务器的DNS名称时,我们可以使用gethostbyname()函数,例如codeproject.com或ftp.myserver.org之类的名称。当我们拥有要连接的服务器的IP地址时,可以使用gethostbyaddr()函数,例如192.168.1.1或202.54.1.100。
显然,我们希望能使我们的最终用户既能使用DNS名称,也能使用IP地址。那么,为了这些工作对他来说透明,我们需要像下面这样玩一个小把戏。我们对入口字符串使用inet_addr(),这个函数会把一个IP地址转换成一个标准的网络地址格式。这样一来,如果它返回失败,我们就可以知道这个字符串不是一个IP地址,如果它成功的话,我们就可以假设它是一个有效的IP地址了。
if(inet_addr(servername)==INADDR_NONE)
{
hp=gethostbyname(servername);
}
else
{
addr=inet_addr(servername);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL)
{
closesocket(conn);
return;
}
连接到服务器
connect()函数用于向目标服务器建立连接。我们向它传递我们先前创建的套接字和一个sockaddr结构。我们使用由gethostbyname()/gethostbyaddr()返回的主机地址为sockaddr成员赋值,并输入一个要连接的有效端口。
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(80);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return;
}
会话
当套接字连接建立后,客户端和服务器就可以通过send()和recv()来发送/接收数据了。这通常称为TCP会话。对于我们的特定情况,我们需要进行HTTP会话。和那些复杂的SMTP或POP3协议相比,它还是比较简单的。HTTP的GET命令用于从HTTP服务器上获取文件。这个文件可以是HTML文件、图像文件、压缩文件、MP3文件等等。这样,这个文件就会被发送了(这是它最简单的形式)。当然,还有一些更复杂的方法来使用这个命令。
GET http-path-to-file\r\n\r\n
在我们的程序中,我们像这样来发送GET命令:
sprintf(buff,"GET%s\r\n\r\n",filepath);
send(conn,buff,strlen(buff),0);
当我们发送了这个命令的时候,我们就应该知道服务器就要开始把我们所请求的文件发送给我们了。就像我们使用send()来发送我们的命令一样,我们可以使用recv()来接收服务器发送给我们的数据。我们循环调用recv(),直到它返回零,这时候我们就会知道服务器已经将数据发送完毕了。并且,对于我们的特定情况,我们可以将这些数据写入文件,就像我们要下载并保存这个文件一样。
while(y=recv(conn,buff,512,0))
{
f.Write(buff,y);
}
关闭连接
现在我们的会话结束了,我们必须关闭连接。在我们的情况下,HTTP连接在文件发送完毕之后就会被服务器关闭了,但是这不要紧,我们仍然需要关闭我们的套接字并释放资源。在更加复杂的会话中,我们通常在调用closesocket()之前调用shutdown()来确定缓冲区已经被刷新,否则可能会有部分数据丢失。
closesocket(conn);
释放WinSock
我们调用WSACleanup()来结束WinSock的使用。
WSACleanup();
关于socket编程函数,socket套接字编程的介绍到此结束,希望对大家有所帮助。