📄 socket.cpp
字号:
/*
* Copyright (C) 2001-2005 Jacek Sieka, arnetheduck on gmail point com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "DCPlusPlus.h"
#include "Socket.h"
#include "SettingsManager.h"
#include "ResourceManager.h"
#include "ServerSocket.h"
string Socket::udpServer;
short Socket::udpPort;
#define checkconnected() if(!isConnected()) throw SocketException(STRING(NOT_CONNECTED))
#ifdef _DEBUG
SocketException::SocketException(int aError) throw() {
error = "SocketException: " + errorToString(aError);
dcdebug("Thrown: %s\n", error.c_str());
}
#else // _DEBUG
SocketException::SocketException(int aError) throw() {
error = errorToString(aError);
}
#endif
Socket::Stats Socket::stats = { 0, 0 };
string SocketException::errorToString(int aError) throw() {
switch(aError) {
case EWOULDBLOCK:
return STRING(OPERATION_WOULD_BLOCK_EXECUTION);
case EACCES:
return STRING(PERMISSION_DENIED);
case EADDRINUSE:
return STRING(ADDRESS_ALREADY_IN_USE);
case EADDRNOTAVAIL:
return STRING(ADDRESS_NOT_AVAILABLE);
case EALREADY:
return STRING(NON_BLOCKING_OPERATION);
case ECONNREFUSED:
return STRING(CONNECTION_REFUSED);
case ETIMEDOUT:
return STRING(CONNECTION_TIMEOUT);
case EHOSTUNREACH:
return STRING(HOST_UNREACHABLE);
case ESHUTDOWN:
return STRING(SOCKET_SHUT_DOWN);
case ECONNABORTED:
return STRING(CONNECTION_CLOSED);
case ECONNRESET:
return STRING(CONNECTION_RESET);
case ENOTSOCK:
return STRING(NOT_SOCKET);
case ENOTCONN:
return STRING(NOT_CONNECTED);
case ENETUNREACH:
return STRING(NETWORK_UNREACHABLE);
default:
{
char tmp[64];
sprintf(tmp, CSTRING(UNKNOWN_ERROR), aError);
return tmp;
}
}
}
void Socket::create(int aType /* = TYPE_TCP */, bool server /* = false */) throw(SocketException) {
if(sock != INVALID_SOCKET)
Socket::disconnect();
switch(aType) {
case TYPE_TCP:
checksocket(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
break;
case TYPE_UDP:
checksocket(sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
break;
default:
dcasserta(0);
}
// Multiple interface fix
if(!server && SETTING(BIND_ADDRESS) != "0.0.0.0") {
sockaddr_in sock_addr;
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(0); // Let stack choose our port
sock_addr.sin_addr.s_addr = inet_addr(SETTING(BIND_ADDRESS).c_str());
::bind(sock, (sockaddr *)&sock_addr, sizeof(sock_addr));
// If it fails, we'll get normal INADDR binding instead...
}
type = aType;
}
/**
* Binds an UDP socket to a certain local port.
*/
void Socket::bind(short aPort) throw (SocketException){
dcassert(type == TYPE_UDP);
sockaddr_in sock_addr;
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(aPort);
// Multiple interface fix
sock_addr.sin_addr.s_addr = inet_addr(SETTING(BIND_ADDRESS).c_str());
if(::bind(sock, (sockaddr *)&sock_addr, sizeof(sock_addr)) == SOCKET_ERROR) {
sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
checksockerr(::bind(sock, (sockaddr *)&sock_addr, sizeof(sock_addr)));
}
connected = true;
}
void Socket::accept(const ServerSocket& aSocket) throw(SocketException){
if(sock != INVALID_SOCKET) {
Socket::disconnect();
}
type = TYPE_TCP;
dcassert(!isConnected());
sockaddr_in sock_addr;
socklen_t sz = sizeof(sock_addr);
checksockerr(sock=::accept(aSocket.getSocket(), (sockaddr*)&sock_addr, &sz));
#ifdef _WIN32
// Make sure we disable any inherited windows message things for this socket.
::WSAAsyncSelect(sock, NULL, 0, 0);
#endif
setBlocking(true);
connected = true;
setIp(inet_ntoa(sock_addr.sin_addr));
}
/**
* Connects a socket to an address/ip, closing any other connections made with
* this instance.
* @param aAddr Server address, in dns or xxx.xxx.xxx.xxx format.
* @param aPort Server port.
* @throw SocketException If any connection error occurs.
*/
void Socket::connect(const string& aAddr, short aPort) throw(SocketException) {
sockaddr_in serv_addr;
hostent* host;
if(sock == INVALID_SOCKET) {
create();
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_port = htons(aPort);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(aAddr.c_str());
if (serv_addr.sin_addr.s_addr == INADDR_NONE) { /* server address is a name or invalid */
host = gethostbyname(aAddr.c_str());
if (host == NULL) {
throw SocketException(STRING(UNKNOWN_ADDRESS));
}
serv_addr.sin_addr.s_addr = *((u_int32_t*)host->h_addr);
}
setIp(inet_ntoa(serv_addr.sin_addr));
if(::connect(sock,(sockaddr*)&serv_addr,sizeof(serv_addr)) == SOCKET_ERROR) {
// EWOULDBLOCK is ok, the attempt is still being made, and FD_CONNECT will be signaled...
if(errno != EWOULDBLOCK) {
checksockerr(SOCKET_ERROR);
}
}
connected = true;
}
/**
* Reads zero to aBufLen characters from this socket,
* @param aBuffer A buffer to store the data in.
* @param aBufLen Size of the buffer.
* @return Number of bytes read, 0 if disconnected and -1 if the call would block.
* @throw SocketException On any failure.
*/
int Socket::read(void* aBuffer, int aBufLen) throw(SocketException) {
checkconnected();
int len = 0;
if(type == TYPE_TCP) {
checkrecv(len=::recv(sock, (char*)aBuffer, aBufLen, 0));
} else if(type == TYPE_UDP) {
checkrecv(len=::recvfrom(sock, (char*)aBuffer, aBufLen, 0, NULL, NULL));
}
stats.totalDown += len;
return len;
}
/**
* Reads zero to aBufLen characters from this socket,
* @param aBuffer A buffer to store the data in.
* @param aBufLen Size of the buffer.
* @param aIP Remote IP address
* @return Number of bytes read, 0 if disconnected and -1 if the call would block.
* @throw SocketException On any failure.
*/
int Socket::read(void* aBuffer, int aBufLen, string &aIP) throw(SocketException) {
checkconnected();
int len = 0;
sockaddr_in remote_addr = { 0 };
socklen_t addr_length = sizeof(remote_addr);
checkrecv(len=::recvfrom(sock, (char*)aBuffer, aBufLen, 0, (sockaddr*)&remote_addr, &addr_length)); //
aIP = string(inet_ntoa(remote_addr.sin_addr));
stats.totalDown += len;
return len;
}
/**
* Reads data until aBufLen bytes have been read or an error occurs.
* On error, an unspecified amount of bytes might have already been read...
*/
int Socket::readFull(void* aBuffer, int aBufLen) throw(SocketException) {
int i = 0;
int j;
while(i < aBufLen) {
if((j = read(((char*)aBuffer) + i, aBufLen - i)) <= 0) {
return j;
}
i += j;
}
return i;
}
/**
* Sends data, will block until all data has been sent or an exception occurs
* @param aBuffer Buffer with data
* @param aLen Data length
* @throw SocketExcpetion Send failed.
*/
void Socket::write(const char* aBuffer, size_t aLen) throw(SocketException) {
checkconnected();
// dcdebug("Writing %db: %.100s\n", aLen, aBuffer);
if(aLen == 0){
return;
}
size_t pos = 0;
size_t sendSize = min(aLen, (size_t)64 * 1024);
bool blockAgain = false;
while(pos < aLen) {
int i = ::send(sock, aBuffer+pos, (int)min(aLen-pos, sendSize), 0);
if(i == SOCKET_ERROR) {
if(errno == EWOULDBLOCK) {
if(blockAgain) {
// Uhm, two blocks in a row...try making the send window smaller...
if(sendSize >= 256) {
sendSize /= 2;
dcdebug("Reducing send window size to %lu\n", sendSize);
} else {
Thread::sleep(10);
}
blockAgain = false;
} else {
blockAgain = true;
}
wait(2000, WAIT_WRITE);
} else if(errno == ENOBUFS) {
if(sendSize > 32) {
sendSize /= 2;
dcdebug("Reducing send window size to %lu\n", sendSize);
} else {
throw SocketException(STRING(OUT_OF_BUFFER_SPACE));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -