我不知道这是一般的网络问题还是仅仅是编程问题所以我决定在这里发布。
尝试制作聊天程序,遇到以下情况:
- 我启动服务器程序;服务器在端口22001上创建一个套接字;服务器等待连接(accept()pending);
- 我启动客户端程序;客户端连接没有错误;客户端发送没有错误;
- 服务器接收消息;
然后:
- 我在同一个端口上启动另一个客户端(另一个客户端程序实例,不停止或断开第一个客户端);
- 第二个客户端连接没有错误(?!)虽然服务器不再处于“accept()pending”状态;
- 第二个客户端发送消息而不显示任何错误(?!);
- 服务器不接收来自第二个客户端的消息(?!);
实验的最后一步:
- 我在服务器端断开插座;在这一刻,两个(?!)客户端在发送时显示错误。
服务器和客户端在同一台计算机上运行,并配置为使用端口22001和机器的IP(例如192.168.123.123)。 我使用带有超时的阻塞套接字进行读写,我使用select()来超时接受()。我使用SO_REUSEADDR。我承认我并不完全了解select()是如何工作的 我知道tcp / ip根据set设置了连接:服务器ip,服务器端口,客户端ip客户端端口。在我的情况下,客户端端口应该是不同的,它们似乎是。但是什么端口是52428(?!),为什么三个都是相同的(见下面的日志)? 使用getpeername()获取远程端口,使用getsockname()获取本地端口 我认为可以在同一端口上的服务器上有多个连接,但不能在同一个套接字上。我错了吗? 请注意,在服务器断开连接后,客户端1显示错误10053,但客户端2显示错误10054 那怎么可能呢?如何在服务器上阻止同一个套接字上的多个连接但允许同一端口上的多个连接?
程序的输出如下:
Server:
1) Skt 0: SocketListen 116 created - Port 22001 IP 192.168.123.123.
2) Skt 0: Wait for client connection/
3) Skt 0: SocketAccept 120 created.
4) Skt 0: Remote port is 52428 (SocketListen).
5) Skt 0: Remote port is 52428 (SocketAccept).
6) Skt 0: Local port is 22001 (SocketListen).
7) Skt 0: Local port is 22001 (SocketAccept).
8) Skt 0: Client connected.
9) Skt 0: InUse.
10) Skt 0: << Src=Cli1= Dst=Cli2= Body=Text_A_0.
11) Skt 0: << Src=Cli1= Dst=Cli2= Body=Text_B_0.
12) Skt 0: closing...
13) Skt 0: Both sockets (Listen and Accept/Connect) droped.
14) Skt 0: closed.
Client 1:
1) Socket 116 created.
2) Socket connected - Port 22001 IP 192.168.123.123.
3) Remote port is 52428.
4) Local port is 62193
5) Sent to Cli2 CliString: =Text_A_0=.
6) Sent to Cli2 CliString: =Text_B_0=.
7) ERROR !!! Port 22001 cannot be reached. Error =10053.
8) ERROR !!! setsockopt( recv_timeout ) failed.
9) Socket closed.
Client 2:
1) Socket 116 created.
2) Socket connected - Port 22001 IP 192.168.123.123.
3) Remote port is 52428.
4) Local port is 62194
5) Sent to Cli1 CliString: =Text_C_0=.
6) Sent to Cli1 CliString: =Text_D_0=.
7) ERROR !!! Port 22001 cannot be reached. Error =10054.
8) ERROR !!! setsockopt( recv_timeout ) failed.
9) Socket closed.
服务器,代码的一部分:
class SktSvr
{
uint32 u32SktIdx;
uint16 u16PortNr;
string strIpAddr;
WSADATA wsaData;
struct sockaddr_in sAddr_Svr;
SOCKET SktListen, SktAccept;
int iRes;
thread * pThreadRecv, * pThreadSend;
mutex Mutex_CliId;
static mutex Mutex_MailBoxes;
static map< string, map< string, string > > MailBoxes; // key = destin , key = source, message
public:
char achCliId[ MSG_SRC_STR_LEN + 1 ];
bool bIsPrep;
atomic<bool> bInUse;
SktSvr( uint32 u32SktIdx_p, uint16 u16PortNr_p, char * pchAddr_p = "127.0.0.1" )
{
u16PortNr = u16PortNr_p;
strIpAddr = pchAddr_p;
u32SktIdx = u32SktIdx_p;
bIsPrep = bInUse = false;
pThreadRecv = pThreadSend = NULL;
achCliId[ 0 ] = '\0';
memset( &sAddr_Svr, 0, sizeof( sAddr_Svr ) );
sAddr_Svr.sin_family = AF_INET; // server byte order
wstring wstrIpAddr( strIpAddr.begin(), strIpAddr.end() );
InetPton( AF_INET, wstrIpAddr.c_str(), &sAddr_Svr.sin_addr.s_addr ); //INADDR_ANY; // host addr
sAddr_Svr.sin_port = htons( u16PortNr );
char chOptVal = 1;
if( ( iRes = WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) ) != NO_ERROR )
{
printf( "\nSkt %d: ERROR !!! WSAStartup failed: %d.", u32SktIdx, iRes );
}
else if( ( SktListen = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) // default protocol
{
printf( "\nSkt %d: ERROR !!! Port %d can not be opened.", u32SktIdx, u16PortNr );
printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
WSACleanup();
}
else if( setsockopt( SktListen, SOL_SOCKET, SO_REUSEADDR, &chOptVal, sizeof( chOptVal ) ) == -1 )
{
printf( "\nSkt %d: ERROR !!! Port %d set options failed.", u32SktIdx, u16PortNr );
printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
WSACleanup();
}
else if( bind( SktListen, ( struct sockaddr * ) &sAddr_Svr, sizeof( sAddr_Svr ) ) == SOCKET_ERROR )
{
printf( "\nSkt %d: ERROR !!! Port %d can not be bound.", u32SktIdx, u16PortNr );
printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
WSACleanup();
}
else if( listen( SktListen, 5 ) == SOCKET_ERROR ) // size for backlog queue = 5
{
printf( "\nSkt %d: ERROR !!! Port %d can not listen.", u32SktIdx, u16PortNr );
printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
WSACleanup();
}
else
{
printf( "\nSkt %d: SocketListen %d created - Port %d IP %s.",
u32SktIdx, SktListen, u16PortNr, strIpAddr.c_str() );
bIsPrep = true;
}
}
bool SktConn()
{
int iRes;
struct timeval tmvl;
fd_set rfds;
FD_ZERO( &rfds );
FD_SET( SktListen, &rfds );
tmvl.tv_sec = TIMEOUT_SKT_CONN_S;
tmvl.tv_usec = 0;
if( 0 ) {}
else if( 1 && ( iRes = select( SktListen + 1, &rfds, (fd_set*)0, (fd_set*)0, &tmvl ) ) <= 0 )
{
// connect timeout
//printf( "\nSkt %d: Socket Connect timeout.", u32SktIdx );
bInUse = false;
}
else if( ! FD_ISSET( SktListen, &rfds ) )
{
printf( "\nSkt %d: Selected another.", u32SktIdx );
}
else if( ( SktAccept = accept( SktListen, NULL, NULL ) ) == INVALID_SOCKET )
{ printf( "\nSkt %d: ERROR !!! Port %d did not connect.", u32SktIdx, u16PortNr );
printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
bInUse = false;
}
else
{
printf( "\nSkt %d: SocketAccept %d created.", u32SktIdx, SktAccept );
struct sockaddr_in sAddr;
socklen_t len;
getpeername( SktListen, ( struct sockaddr* )&sAddr, &len );
printf( "\nSkt %d: Remote port is %d (SocketListen).", u32SktIdx, ntohs( sAddr.sin_port ) );
getpeername( SktAccept, ( struct sockaddr* )&sAddr, &len );
printf( "\nSkt %d: Remote port is %d (SocketAccept).", u32SktIdx, ntohs( sAddr.sin_port ) );
int iAddrLen = sizeof( sAddr );
if( getsockname( SktListen, ( struct sockaddr * )&sAddr, &iAddrLen ) == 0 &&
sAddr.sin_family == AF_INET && iAddrLen == sizeof( sAddr ) )
printf( "\nSkt %d: Local port is %d (SocketListen).", u32SktIdx, ntohs( sAddr.sin_port ) );
if( getsockname( SktAccept, ( struct sockaddr * )&sAddr, &iAddrLen ) == 0 &&
sAddr.sin_family == AF_INET && iAddrLen == sizeof( sAddr ) )
printf( "\nSkt %d: Local port is %d (SocketAccept).", u32SktIdx, ntohs( sAddr.sin_port ) );
bInUse = true;
}
return bInUse;
}
答案 0 :(得分:1)
单个侦听套接字用于接受单个端口上的任意数量的连接。对于每个连接,由public class FileUploader {
private final String boundary;
private static final String LINE_FEED = "\r\n";
private HttpURLConnection httpConn;
private String charset;
private OutputStream outputStream;
private PrintWriter writer;
public FileUploader(String requestURL, String charset)
throws IOException {
this.charset = charset;
// creates a unique boundary based on time stamp
boundary = "===" + System.currentTimeMillis() + "===";
URL url = new URL(requestURL);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setUseCaches(false);
httpConn.setDoOutput(true); // indicates POST method
httpConn.setDoInput(true);
httpConn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + boundary);
httpConn.setRequestProperty("User-Agent", "CodeJava Agent");
httpConn.setRequestProperty("Test", "Bonjour");
outputStream = httpConn.getOutputStream();
writer = new PrintWriter(new OutputStreamWriter(outputStream, charset),
true);
}
/**
* Adds a form field to the request
* @param name field name
* @param value field value
*/
public void addFormField(String name, String value) {
writer.append("--" + boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"" + name + "\"")
.append(LINE_FEED);
writer.append("Content-Type: text/plain; charset=" + charset).append(
LINE_FEED);
writer.append(LINE_FEED);
writer.append(value).append(LINE_FEED);
writer.flush();
}
/**
* Adds a upload file section to the request
* @param fieldName name attribute in <input type="file" name="..." />
* @param uploadFile a File to be uploaded
* @throws IOException
*/
public void addFilePart(String fieldName, File uploadFile)
throws IOException {
String fileName = uploadFile.getName();
writer.append("--" + boundary).append(LINE_FEED);
writer.append(
"Content-Disposition: form-data; name=\"" + fieldName
+ "\"; filename=\"" + fileName + "\"")
.append(LINE_FEED);
writer.append(
"Content-Type: "
+ URLConnection.guessContentTypeFromName(fileName))
.append(LINE_FEED);
writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
writer.append(LINE_FEED);
writer.flush();
FileInputStream inputStream = new FileInputStream(uploadFile);
byte[] buffer = new byte[4096];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
inputStream.close();
writer.append(LINE_FEED);
writer.flush();
}
/**
* Adds a header field to the request.
* @param name - name of the header field
* @param value - value of the header field
*/
public void addHeaderField(String name, String value) {
writer.append(name + ": " + value).append(LINE_FEED);
writer.flush();
}
/**
* Completes the request and receives response from the server.
* @return a list of Strings as response in case the server returned
* status OK, otherwise an exception is thrown.
* @throws IOException
*/
public List<String> finish() throws IOException {
List<String> response = new ArrayList<String>();
writer.append(LINE_FEED).flush();
writer.append("--" + boundary + "--").append(LINE_FEED);
writer.close();
// checks server's status code first
int status = httpConn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpConn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
response.add(line);
}
reader.close();
httpConn.disconnect();
} else {
throw new IOException("Server returned non-OK status: " + status);
}
return response;
}
创建并返回一个新套接字,即“每个连接一个套接字”进入的位置。您从未听过第二个客户端发送的消息的原因是您只有accept()
从连接到第一个客户端的套接字。
如果客户端连接时侦听套接字不在recv()
调用内,则传入连接将排队。这是标准的套接字行为,您不仅可以在Windows上看到它,还可以在BSD,Linux等中看到它。
接受队列的大小是套接字选项。
如果您不想要排队行为,则必须在接受第一个连接后立即关闭侦听套接字。但几乎可以肯定你想要的是accept()
(或select()
,WSAEventSelect
等)监视客户端套接字和监听套接字,当监听套接字显示活动时,调用再次poll
并同时激活多个连接到客户端的套接字。