SOCK如何知道远端请求的IP和端口号。(100分)

  • 主题发起人 主题发起人 lingru
  • 开始时间 开始时间
L

lingru

Unregistered / Unconfirmed
GUEST, unregistred user!
这个问题的提出基于以下背景:
一般情况下双方通信的端口是可以约定的。但如果一个局域网(例如网吧)的两个机器同时
与服务器进行通信,那么在局域网服务器将网内地址和端口转换成服务器地址不同端口向
远端的通信服务器发送数据。在这个情况下数据是可以到达的。
那么在通信服务器向网吧里面的机器发送消息时,如果还利用原先协商的端口,数据是不能
到达指定机器的吧?是不是需要知道客户端映射的那个对外端口?怎样才能知道?
好象getpeername()只能得到IP吧?端口号……
 
如果采用长连接:客户机连接服务器,服务器接受连接请求后,两端的连接就已经建立了,
如果有需要的话,应该建立连接描述符与客户端地址之间的对应关系。建立连接后不要断开,
直接往连接管道里发数据就可以了。
(连接描述符被封装为TCustomWinSocket的Handle成员,客户端地址被封装为TCustomWinSocket
的RemoteAddr或RemoteAddress, RemotePort成员,如果你用是Winsock API来编程的话,客
户端地址可以在accept时获得,是一个sockaddr_in类型的结构。)

如果使用短连接:网吧服务器和客户机上都建立Socket服务端并保持运行,网吧服务器通过
地址表(包括地址和端口号的文件或数据表)读取地址,连接客户机上的socket服务端,并
把指令发给客户机,指令执行完后断开。这样每台客户机的socket服务端口号可以都是一样
的,只是地址不同而已。
 
情况这是这样的“OICQ之间的通迅应该是这样的,当你登录时,会QQ服务器提供你当前所在的IP地址各端口
号,然后当你的好友上线时,就会从QQ服务得到这个IP和Port,以后的通迅才能继续!
上面可能没说得太清楚,下面举个例子来解释,
比如说,你在一家网吧里上网,这个网吧的服器有一个真正的IP地址(可能是临时的--对于
拨号上网;或者是固定的--对于专线上网)比如是61.168.45.120,同时有一个网吧局域网
的IP地址,比如192.168.0.1。而你在该网吧内IP地址为192.168.0.3的机器上用OICQ,当你
上线时,QQ会按你设定的路由发送数据(对于网吧,一般是设IP为192.168.0.3的机器的默
认网关为网吧的服务器,即192.168.0.1的机器)把你所在机器的IP(192.168.0.3)和Port
(一般第一个QQ为4000,二个为4001,依次类推,这时设为4000)通知网吧服器,由于网吧
的服务器(也可说是一个网关)要同时满足多台机器上网的需要,会在它的局域网地址(
即192.168.0.1上)动态的分配一个Port(如 4782)与你通迅,同时再在真正的IP(
即61.168.45.120)上分配一个Port(比如 5472)将你的数据发送到互联网上,这里即为向
QQ服务器发出它的IP和Port(这些都是在网吧服务器上运行的一个代理软件 如WinGate、
SyGate等 所做操作)。
{注意:这个发出的IP是网吧服务器的那个真正IP,Port为相对于这个IP的一个端口(5472)}
现在QQ服务器就把你的QQ当着是在IP为61.168.45.120的机器的Port 5472上运行了,当你的
好友上线时,它就会得到这个IP和Port,然后和你通迅,即把所有发给你的数据都发到
IP为61.168.45.120的机器的5472 Port上,然后网吧服务器上所装的代理软件(比如
WinGate、SyGate等)就会把这些数据通过局域网地址(IP:192.168.0.1,Port:4782)转发到
你所在的机器(192.168.0.3)的相应Port(4000)上,即完成通迅。”

现在的问题是,接受数据的一端,也可能就是上面所说的QQ服务器怎样才能知道发过来数据
一端的IP 和 PORT?
 
用recvfrom()函数(而非recv()函数)接收即可得知对方IP和端口

int recvfrom (
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR* from,
int FAR* fromlen
);

接收后将struct sockaddr FAR* from强制转换成sockaddr_in

struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};

其中包含了IP和Port。
 
我用UDP举个例子,TCP类似。

unit udp;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, winsock,
StdCtrls;

const
WM_SOCK = WM_USER + 1; //自定义windows消息
UDPPORT = 646; //设定UDP端口号

type
Tfrmmain = class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
s: TSocket;
addr: TSockAddr;
FSockAddrIn : TSockAddrIn;
//利用消息实时获知UDP消息
procedure ReadData(var Message: TMessage);
message WM_SOCK;
public
{ Public declarations }
procedure SendData(Content: String);
end;

var
frmmain: Tfrmmain;

implementation

{$R *.DFM}

procedure Tfrmmain.FormCreate(Sender: TObject);
var
TempWSAData: TWSAData;
//optval: integer;
begin
// 初始化SOCKET
if WSAStartup($101, TempWSAData)=1 then
showmessage('StartUp Error!');

s := Socket(AF_INET, SOCK_DGRAM, 0);
if (s = INVALID_SOCKET) then //Socket创建失败
begin
showmessage(inttostr(WSAGetLastError())+' Socket创建失败');
CloseSocket(s);
// exit;
end;
//发送方SockAddr绑定
addr.sin_family := AF_INET;
addr.sin_addr.S_addr := INADDR_ANY;
addr.sin_port := htons(UDPPORT);
if Bind(s, addr, sizeof(addr)) <> 0 then
begin
showmessage('bind fail');
end;

WSAAsyncSelect(s, frmmain.Handle , WM_SOCK, FD_READ);
//接收端SockAddrIn设定
FSockAddrIn.SIn_Family := AF_INET;
FSockAddrIn.SIn_Port := htons(UDPPORT);
label3.Caption := '端口:'+inttostr(UDPPORT);
end;

procedure Tfrmmain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseSocket(s);
end;

procedure Tfrmmain.ReadData(var Message: TMessage);
var
buffer: Array [1..4096] of char;
len: integer;
flen: integer;
Event: word;
value: string;
begin
flen:=sizeof(FSockAddrIn);
FSockAddrIn.SIn_Port := htons(UDPPORT);
Event := WSAGetSelectEvent(Message.LParam);
if Event = FD_READ then
begin
//注意这里得到IP和Port<------------------------------------------------------attention
len := recvfrom(s, buffer, sizeof(buffer), 0, FSockAddrIn, flen);
value := copy(buffer, 1, len);
value := value + format(' port:%d, addr:%s',[FSockAddrIn.sin_port,inet_ntoa(FSockAddrIn.sin_addr)]);
Memo1.Lines.add(value)
end;
end;

procedure Tfrmmain.SendData(Content: String);
var
value{,hostname}: string;
len: integer;
begin

FSockAddrIn.SIn_Addr.S_addr := inet_addr(pchar(Edit1.text)); //INADDR_BROADCAST; //INADDR_BROADCAST = -1 ?
value := Content;
len := sendto(s, value[1], Length(value), 0, FSockAddrIn, sizeof(FSockAddrIn));
if (WSAGetLastError() <> WSAEWOULDBLOCK) and (WSAGetLastError() <> 0) then
showmessage(inttostr(WSAGetLastError()));
if len = SOCKET_ERROR then
showmessage('send fail');
if len <> Length(value) then
showmessage('Not Send all');
end;

procedure Tfrmmain.Button1Click(Sender: TObject);
begin
senddata(Edit2.text);
end;

end.
 
TCP和UDP情况不一样的吧?
在里面recvfrom的最后两个参数失效吧?请指教
 
TCP是个有连接的协议,你在Connect的时候就可以得到对方的地址和端口了,给段代码你参考:
{--------------------------------------------------------------
Simple Example.
Implement TCP(both Client and Server) with Socket API
<zw84611@sina.com>
--------------------------------------------------------------}

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
WinSock, StdCtrls;

const
WM_SOCK = WM_USER + 1; //自定义windows消息
TCP_PORT = 5432; //设定TCP端口号

type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
HasConnected, IsServer: boolean;
CliSocket, SvrSocket: integer;
SvrAddrIn, CliAddrIn:TSockAddrIn;
procedure InitSocket;
procedure SendData(Content: string);
procedure ReadData(var Message: TMessage); message WM_SOCK;
procedure SockConnect;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.InitSocket;
var
wsadata: TWsadata;
err{, optval}: integer;
begin

WSAStartup($0101,WSAData);

CliSocket := socket(AF_INET, SOCK_STREAM,IPPROTO_IP);
SvrSocket := socket(AF_INET, SOCK_STREAM,IPPROTO_IP);

if (CliSocket = INVALID_SOCKET)or(SvrSocket = INVALID_SOCKET) then
begin
ShowMessage(inttostr(WSAGetLastError())+' Socket创建失败');
CloseSocket(CliSocket);
exit;
end;

SvrAddrIn.sin_addr.s_addr:= INADDR_ANY;
SvrAddrIn.sin_family := AF_INET;
SvrAddrIn.sin_port :=htons(TCP_PORT);
Bind(SvrSocket, SvrAddrIn, sizeof(SvrAddrIn));

err := Listen(SvrSocket,5);
if err<>0 then ShowMessage('Listen error.');

{optval := 1;
if SetSockopt(SvrSocket,SOL_SOCKET,SO_REUSEADDR,pchar(@optval),sizeof(optval)) = SOCKET_ERROR then
begin
showmessage('SO_REUSEADDR set error.');
end; }

//绑定消息映射
WSAAsyncSelect(SvrSocket, Handle , WM_SOCK, FD_READ or FD_ACCEPT or FD_CONNECT or FD_WRITE or FD_CLOSE);
WSAAsyncSelect(CliSocket, Handle , WM_SOCK, FD_READ or FD_ACCEPT or FD_CONNECT or FD_WRITE or FD_CLOSE);

end;

procedure TForm1.SockConnect;
var
err: integer;
begin

CliAddrIn.sin_addr.s_addr:=inet_addr(PChar(Edit1.Text));
CliAddrIn.sin_family := AF_INET;
CliAddrIn.sin_port :=htons(TCP_PORT);
repeat
err:=connect(CliSocket,CliAddrIn, SizeOf(CliAddrIn));
if err = -1 then
begin
{ if we use WSAAsyncSelect(CliSocket...) in order to receive data at
Client side, here will get error, but it still works. why?
}
//ShowMessage('connect error.');
//ListBox1.Items.Add('connect error.');
HasConnected := false;
break;
end
else
begin
HasConnected := true;
IsServer := false;
end;
until err=0;

end;

procedure TForm1.SendData(Content: string);
begin
Send(CliSocket,Content[1],length(Content),0);
end;

procedure TForm1.ReadData(var Message: TMessage);
var
Event: word;
Buf:array[0..1023] of char;
AddrLen, DataLen: integer;
begin
//
AddrLen := sizeof(SvrAddrIn);
Event := WSAGetSelectEvent(Message.LParam);

case Event of
FD_CONNECT:
begin
ListBox1.Items.Add('connect');
HasConnected := true;
//do nothing?
end;
FD_ACCEPT:
begin
IsServer := true;
HasConnected := true;
ListBox1.Items.Add('accept');
//CloseSocket(CliSocket);
CliSocket := Accept(SvrSocket,@SvrAddrIn,@AddrLen);
ListBox1.Items.Add(format(' port:%d, addr:%s',[SvrAddrIn.sin_port,
inet_ntoa(SvrAddrIn.sin_addr)])); //注意这里,得到地址和Port<----------------------------------------------------------------attention
end;
FD_READ:
begin
DataLen := Recv(CliSocket,Buf,1024,0);
buf[DataLen] := #0;
ListBox1.Items.Add(Buf);
{DataLen := RecvFrom(CliSocket,Buf,sizeof(buf),0,CliAddrIn,AddrLen);
ListBox1.Items.Add(format(' port:%d, addr:%s',[CliAddrIn.sin_port,
inet_ntoa(CliAddrIn.sin_addr)])+strpas(buf));}
end;
FD_WRITE:
begin
ListBox1.Items.Add('write');
end;
FD_OOB:
begin
ListBox1.Items.Add('FD_OOB');
end;
FD_CLOSE:
begin
HasConnected := false;
ListBox1.Items.Add('close');
end;
end; //end of case
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
//202.104.32.230
if (not IsServer)and(not HasConnected) then SockConnect;
SendData('hello, world');
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseSocket(SvrSocket);
CloseSocket(CliSocket);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
HasConnected := false;
IsServer := false;
InitSocket;
end;

end.
 
zw84611,谢谢
 
是这样的,TCP是有连接的,在连接过程中,主动者为Client,被动者为Server,但这并不
意味着传输是单向的。TCP是双向的,在一个TCP连接中Client可以发信息,也可以收信息,Server可以收信息,
同样也可以发信息。一旦连接建立起来,Server就可以向Client发信息了,而不必做额外的查找
Client的IP之类的工作。
 
zw84611,你说的我也了解,但是,如果是不同两个网吧内的两个机器建立TCP
 
我不太明白,TCP和地理位置有什么关系吗?
 
zw84611,你说的我理解,但如果是不同两个网吧内的两个机器建立TCP连接,不是需要对方
的服务器IP地址和服务器分配给自己的对外PORT吗?
 
对,Client必须事先知道Server的IP和Port
 
getpeername 可以获得port
 
所以说事先获得SERVER的IP和PORT是重要的吧?除非做出来的TCP只面对专线或者拨号接入
的计算机。问题怎样解决呢?
 
什么问题?Client事先不知道Server的IP和Port是不可能通信的。
 
多人接受答案了。
 
后退
顶部