1 頁 (共 1 頁)

無法 listen port?

發表於 : 10/28/2004 11:32 pm
ulysses
我為了測試網路通訊協定,寫了一個 listen TCP port 的程式,動作很簡單,就只是把接收到的東西印在螢幕上。雖說是寫,其實是從 Wrox 那本 Programming Linux 抄下來的,code 如下:

代碼: 選擇全部

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP	"192.168.1.200"
#define SERVER_PORT	9876

int main(void) {

	int bytes;
	int server_fd,  client_fd;
	int server_len, client_len;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	char buffer[255];

	server_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(server_fd<0) {
		perror("Can not create Socket");
		_exit(1);
	}
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_aton(SERVER_IP,&server_addr.sin_addr);
	bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	listen(server_fd, 5);
	while(1) {
		printf("server ready\n");
		client_len = sizeof(client_addr);
		client_fd = accept(server_fd, 
			(struct sockaddr *)&client_addr, &client_len);
		while((bytes=read(client_fd, buffer, 255))>0) {
			buffer[bytes]=(char)NULL;
			printf(buffer);
		}
		close(client_fd);
	}
	_exit(0);
}
放在 OS X 上用 gcc 編譯、執行都無問題,但是 client 端卻一直無法建立連線。我到 Share 下檢查過防火牆設定,沒有開啟。檢查過網路,可以排除所有 IP 設定錯誤、網路連線不通等等雜七雜八的失誤。後來我把整段程式搬到 Linux 機器上,編譯執行,用同一個 client 端連,卻可以成功建立連線。

請問為什麼會這樣呢?我確定 OS X 可以用 Socket 的方式建立網路連線,我使用同樣來源的 Client 端程式在 Mac 上跑,連到 Linux Server,就可以動作。難道 OS X 的 Socket Listen 都要經過系統 API 登記之後才允許開放?
搞不懂...

Re: 無法 listen port?

發表於 : 10/29/2004 12:28 am
digdog
ulysses 寫:放在 OS X 上用 gcc 編譯、執行都無問題,但是 client 端卻一直無法建立連線。我到 Share 下檢查過防火牆設定,沒有開啟。檢查過網路,可以排除所有 IP 設定錯誤、網路連線不通等等雜七雜八的失誤。後來我把整段程式搬到 Linux 機器上,編譯執行,用同一個 client 端連,卻可以成功建立連線。
你這隻在 PowerMac G4 Cube/Mac OS X 10.3.5/gcc 3.3-20030304-apple_build1666 下跑很正常。

你要不要抓回去試看看?

Re: 無法 listen port?

發表於 : 10/29/2004 12:32 am
ulysses
digdog 寫:你這隻在 PowerMac G4 Cube/Mac OS X 10.3.5/gcc 3.3-20030304-apple_build1666 下跑很正常。
:shock: :x :evil: :cry: (無言...)

Re: 無法 listen port?

發表於 : 10/30/2004 10:42 pm
ulysses
digdog 寫:你這隻在 PowerMac G4 Cube/Mac OS X 10.3.5/gcc 3.3-20030304-apple_build1666 下跑很正常。
你要不要抓回去試看看?
我的 gcc 環境和你是一樣的。
經過多次測試,我決定這是 GCC 的問題。怎麼說呢,

原本第 20 行是這樣:

代碼: 選擇全部

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[255];
改成這樣就不行:

代碼: 選擇全部

char buffer[255];
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
更誇張的,改成這樣也不行

代碼: 選擇全部

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[255];
int wxyz;
我認為是 gcc 分配記憶空間出了鎚,要不就是 OS X 內部有什麼鬼,讓同一隻程式的執行結果都不相同。

發表於 : 10/31/2004 11:24 am
藤原佐為
那如果改成 char buffer[256]; or char buffer[300] 呢?
另外,while(1) 無限迴圈不是好的寫法.....

請問你的 GCC 是哪個版本?是這個嗎?

— Mac OS X 10.2.6 or later
— Developer tools with the update of GCC to 3.3

我在 Apple 找不到 download GCC 3.3 的地方....

發表於 : 10/31/2004 1:46 pm
janusng
個人認為用 while(1) 沒有什麼問題,得多時用 break 是比較好用和簡單。況且 while(1) /for(::) 等寫法,幾乎每一個程式也有使用,非常 common。

發表於 : 10/31/2004 2:03 pm
ulysses
brent 寫:那如果改成 char buffer[256]; or char buffer[300] 呢?
應該說,我完全搞不清楚到底出了啥問題。以上說的已經進入工程師的巫醫療法範疇 :twisted:

順便一提,buffer 大小的界限是在 450~480 左右 :twisted:
brent 寫:另外,while(1) 無限迴圈不是好的寫法.....
全看你在什麼環境下開發程式。非多作業環境,像 OS 9,用 while(1) 會出鎚。而在純多工作業環境中,這只是要一個程序(Process)重複執行裡面的工作而已。

上面的無限迴圈中一開始就會碰到 accept 這個 blocking 形態的 function 呼叫,程式就會進入等待狀態,不會一直耗用 CPU 時間。

要結束陷入無限循環的程式,有幾種方法:

1. 不理它,等它自己 core dump :twisted: :twisted: :twisted: 我曾經在 SunOS 上測試過失控的遞迴呼叫,如果有 pass value,大概遞迴到第一千多層就爆了。沒有 pass value 的話,大概可以跑到一萬多層。

2. 送個會停止執行的訊號給它,使用 kill 指令。溫和一點就 kill -SIGINT 或 kill -SIGTERM,有些程式會加上 TERM 的 Signal handler 作一些垃圾清除動作再退出,暴力一點就直接 kill -9 送強制終止訊號出去。
請問你的 GCC 是哪個版本?是這個嗎?
— Mac OS X 10.2.6 or later
— Developer tools with the update of GCC to 3.3
我在 Apple 找不到 download GCC 3.3 的地方....
我用的是 Xcode 1.5 裝的版本。

Anyway,我已經放棄自己寫 listen port 了,改用 Port Test。RB 弄的 Carbon 程式,難看一點沒關係可以用就好。

發表於 : 10/31/2004 2:20 pm
janusng
ulysses 寫:
brent 寫:另外,while(1) 無限迴圈不是好的寫法.....
全看你在什麼環境下開發程式。非多作業環境,像 OS 9,用 while(1) 會出鎚。而在純多工作業環境中,這只是要一個程序(Process)重複執行裡面的工作而已。
用什麼 flow control,同 OS 無關係吧? :roll:

難度 OS 9 中就不能在 while loop 中置入 WaitNextEvent() 了嗎? :x

反而真正的問題在於以上程式沒有辦法 exit 個 infinitely loop! :evil:


發表於 : 10/31/2004 3:45 pm
ulysses
janusng 寫:用什麼 flow control,同 OS 無關係吧? :roll:
難度 OS 9 中就不能在 while loop 中置入 WaitNextEvent() 了嗎? :x
反而真正的問題在於以上程式沒有辦法 exit 個 infinitely loop! :evil:
說?,倒是離題越來越遠。 :oops:
以上程式的 while(1) 目的很單純就是不要程式結束,如果 while loop 中沒有加上讓程式執行擱置的呼叫例如 sigsuspend、sleep 或是 accepe、open 之類,在單工作業的 OS 下就有可能把所有工作排程通通佔掉。如果是在 OS9 時代用 CodeWarrior,我絕對不敢這樣寫。這樣寫的話,就可能會變成要按下 cmd+option+esc 進入 interrupt 狀態才能跳出來。而在 UNIX 上,有時候我會不小心把一個 read() 弄成 Nonblock 狀態,讓程式從同步等候變成循序掃描,而在使用時一點差異都沒有。

再說 WaitNextEvent,雖然說 sigsuspend 也是讓程序等待下一個 Signal 進來,但是 Signal 本身是以“Interrupt”的方式在執行,除非有用 signal mask 把一個訊號 pending 掉,否則 Signal 與 WaitNextEvent 的執行流程完全不同。在 WaitNextEvet 的架構下流程是這樣:

1. 程式正在忙著做 I/O 和計算
2. 有 Event 進來了
3. 程式沒有立刻處理,Event 先堆在 Queue 中
4. 程式還在忙著做 I/O 和計算...
5. 同上.
6. 同上..
7. ...
.
.
.
N. 終於做完了
N+1. 回到 WaitNextEvent,把冰凍了好久的 Event 從 Queue 裡面拿出來...

而 UNIX Signal 則是:

1. 程式正在忙著做 I/O 和計算
2. 有 Signal 進來了
3. 程式正在進行的 I/O 與計算立刻擱置,不管你現在在哪一行,都通通進去 Interrupt 處理程序
4. Interrupt 處理程序完成,回去處理剛剛的工作

單工與多工系統在 Flow control 上最大的不同,就是在這個 Multiple Inter-Process Communication 上。多工系統的同步作業是個非常難處裡的問題。


【回到正題】

我在上面的程式碰到的最大問題就是我找不到成敗的規律。有時候前一種寫法可以,加上完全無關的一兩行程式碼重新編譯執行,就不行了。把那一兩行程式刪掉再重新編譯,又可以了。我還是認為是 GCC 的問題。

發表於 : 10/31/2004 3:49 pm
ulysses
繼續用巫醫療法測試:最後終於發現,只要把 struct sockaddr_in server_addr 的宣告放在 main 外面變成 global,就不會有問題。果然還是 gcc 的問題。

無論如何謝謝各位的幫忙,把完整原始碼(包含參數設定、signal 處理)PO 在下面:

代碼: 選擇全部

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define DEFAULT_SERVER_IP	"127.0.0.1"
#define DEFAULT_SERVER_PORT	9876

struct	sockaddr_in server_addr;
int		server_fd = 0;

void terminate(int sig) {
	if(server_fd>0) {
		close(server_fd);
		puts("[socket closed]");
	}
	_exit(0);
}

int main(int argc, char **argv) {

	int		bytes, opt, use_default=0;
	int		my_port = 0;
	int		client_fd;

	struct sigaction	sigreg;

	char	my_ip[20];
	char	buffer[255];

	bzero(buffer,sizeof(buffer));
	bzero(my_ip,sizeof(my_ip));

	if(argc>1) {
		while ((opt=getopt(argc,argv,"i:p:t:b:d"))!=-1)
		{
			switch (opt)
			{
				case 'i':
					strcpy(my_ip,optarg);
					break;
				case 'p':
					my_port = atoi(optarg);
					break;
				case 'd':
					use_default = 1;
					break;
				default :
					printf("usage: listen -i [ip] -p [port]\n");
					_exit(0);
			}
		}
	}

	if(use_default) {
		if(strlen(my_ip)<=0){
			strcpy(my_ip,DEFAULT_SERVER_IP);
		}
		if(my_port<=0){
			my_port = DEFAULT_SERVER_PORT;
		}
	}
	if( my_port<=0 || strlen(my_ip)<=0	) {
		printf("usage: listen -i [ip] -p [port]\n");
		exit(0);
	}

/*  register signal handler */

	sigreg.sa_handler = terminate;
	sigreg.sa_flags = 0;
	sigemptyset(&sigreg.sa_mask);
	sigaddset(&sigreg.sa_mask,SIGTERM);
	sigaddset(&sigreg.sa_mask,SIGHUP);
	sigaddset(&sigreg.sa_mask,SIGALRM);
	sigaddset(&sigreg.sa_mask,SIGPIPE);
	sigaddset(&sigreg.sa_mask,SIGUSR1);
	sigaddset(&sigreg.sa_mask,SIGUSR2);
	sigaction(SIGINT, &sigreg, 0);

	sigemptyset(&sigreg.sa_mask);
	sigaddset(&sigreg.sa_mask,SIGINT);
	sigaddset(&sigreg.sa_mask,SIGHUP);
	sigaddset(&sigreg.sa_mask,SIGALRM);
	sigaddset(&sigreg.sa_mask,SIGPIPE);
	sigaddset(&sigreg.sa_mask,SIGUSR1);
	sigaddset(&sigreg.sa_mask,SIGUSR2);
	sigaction(SIGTERM, &sigreg, 0);

/*	Name the socket.  */

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(my_port);
	if(inet_aton(my_ip,&(server_addr.sin_addr))==0) {
		fprintf(stderr,"invalid address: %s [%s]\n",
			my_ip,strerror(errno));
		exit(-1);
	}

/*	Create an unnamed socket for the server.  */

	server_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(server_fd<0) {
		perror("can not create socket");
		_exit(1);
	}

	if(bind( server_fd,
			 (struct sockaddr *)&server_addr,
			 sizeof(server_addr) ) == -1 ) {
		fprintf(stderr,"error when bind to %s:%d (%08x)\n[%d]: %s\n",
			my_ip,my_port,server_addr.sin_addr,errno,strerror(errno));
		exit(-1);
	}

/*	Create a connection queue and wait for clients.	 */

	if(listen(server_fd, 5)==-1) {
		perror("error when listen port\n");
		exit(-1);
	}
	while(1) {

		printf("[%s:%d] standby\n",my_ip,my_port);

/*	Accept a connection.  */

		client_fd = accept(server_fd,(struct sockaddr *)NULL,(int *)NULL);
		if(client_fd<=0) {
			perror("error when accept connection");
			exit(-1);
		}

/*	We can now read/write to client on client_fd.  */

		while((bytes=read(client_fd, buffer, 255))>0) {
			buffer[bytes]=(char)NULL;
			printf(buffer);
		}
		close(client_fd);
		
		printf("[%s:%d] socket closed\n",my_ip,my_port);
	}
	_exit(0);
}

發表於 : 10/31/2004 5:57 pm
janusng
ulysses 寫:在 WaitNextEvet 的架構下流程是這樣:

1. 程式正在忙著做 I/O 和計算
2. 有 Event 進來了
3. 程式沒有立刻處理,Event 先堆在 Queue 中
4. 程式還在忙著做 I/O 和計算...
5. 同上.
6. 同上..
7. ...
.
.
.
N. 終於做完了
N+1. 回到 WaitNextEvent,把冰凍了好久的 Event 從 Queue 裡面拿出來...

而 UNIX Signal 則是:

1. 程式正在忙著做 I/O 和計算
2. 有 Signal 進來了
3. 程式正在進行的 I/O 與計算立刻擱置,不管你現在在哪一行,都通通進去 Interrupt 處理程序
4. Interrupt 處理程序完成,回去處理剛剛的工作

單工與多工系統在 Flow control 上最大的不同,就是在這個 Multiple Inter-Process Communication 上。多工系統的同步作業是個非常難處裡的問題。
這個也和 while(1) 無關係,請問在 OS 9之下,你用什麼辦法可以不用等到N. 終於做完了,便回應的?不論用不用 while(1) ,恐怕也是不可能吧?這個是 OS 問題,和 while(1) 無關。

發表於 : 10/31/2004 11:35 pm
ulysses
janusng 寫:這個也和 while(1) 無關係,請問在 OS 9之下,你用什麼辦法可以不用等到N. 終於做完了,便回應的?不論用不用 while(1) ,恐怕也是不可能吧?
在單工作業環境下,的確會有你說的狀況發生。但是要讓它不用等到 “終於做完了” 才回應,還是有辦法做到的:使用 multi-thread 來分別執行計算、監督與界面。這種做法我曾經用過,確定它管用。當然這樣做效率不是很好看。
janusng 寫:這個是 OS 問題,和 while(1) 無關。
離題越來越遠了。回到這個話題最早的起源:
ulysses 寫:
brent 寫:另外,while(1) 無限迴圈不是好的寫法.....
全看你在什麼環境下開發程式。
while(1),說到底,它只是一種流程控制技巧而已,就好像 if、for,以及 goto。每個人有不同的認定與寫作習慣,只要你的程式碼能夠 work,就無所謂好壞。不管黑貓白貓,會抓老鼠的就是好貓。

但是當問說 while(1) 放在程式中適不適當,我會回答:這應該要看使用的時機與地點。怎麼說呢?舉個例子,你要去跟女朋友約會時,會送她一束菊花嗎?你要去參加葬禮,你會送往生者家屬一束紅玫瑰嗎?菊花和玫瑰花本身沒有錯,但是要看時間和地點。

以我上面的程式為例:

1. 如果這是給 OS9 用的程式,我會說『這樣做不恰當,應該要把無限迴圈放在另一個 thread 中,以免把系統鎖死。』

2. 如果這是給 UNIX 用的程式,我會說『OK,這樣做適合作為一個 daemon 或是背景執行的伺服器程式,只是 Log 與 Signal 處理應該要再完整些。』

同樣的 while(1) 在不同的作業環境下,就應該有不同的適用時機與適用地點。這是因為在不同的作業環境中,同一段 while 控制程式碼未必會有同樣的執行路徑與反應。所以,while(1) 不是好不好的問題,而是使用的時機與地點適不適當、系統環境與程式結構能不能符合需求的問題。


退後一步,不要用這麼嚴肅的程式分析方式來看這件事,單就從很單純的『一個工程師在寫程式的時候會怎麼做』來想:他寫一段程式碼,一定會要測試測試。那麼他在 OS9 上執行和在 OS X 上執行,就會有不同的結果:

a. 在 OS9 下,如果中斷點旗標沒有處理好(這是很可能的),又沒有用 thread 等程式技巧(通常不會),while(1) 可能會搞到必須強制關機。

b. 在 OS X 下就不必想這麼多,記憶體保護讓程式不可能抓狂到把系統搞砸,系統中斷支援讓沒有中斷點旗標的 while(1) 迴圈可以隨時終止。

因此,我在寫程式的時候:

i. 在 OS9 下就不喜歡用 while(1) 來控制,因為在程式開發的時候難免出鎚,一旦一個 break 的條件沒有弄對,結果就很難看。所以我能免則免。

ii. 在 OS X 下我就沒有那麼多顧慮,開兩個 terminal 一個跑程式一個跑 ps + kill,while(1) 可以就用的很高興。

也就是說,當我在思考 flow control 的方式時,不同的作業環境特性影響我的決定。

這是主觀的寫作風格問題。每個人寫作有不同的習慣有不同的風格,我寫程式寫了幾十年,這是我的寫作習慣。你可以不同意我的論點,但是我養成這種習慣自有我自己的道理。

發表於 : 10/31/2004 11:43 pm
digdog
哇,這是數週來看到最精彩的討論,拍拍手~ :D

受益良多,受益良多。

--
請原諒我的私心將此討論置頂讓人看個夠~

發表於 : 11/01/2004 8:19 am
ulysses
可是到最後我的問題還是沒有解決 :oops: :oops: :oops:
把一個變數從 local 搬到 global,這是巫醫療法... :oops: :oops: :oops:
我看我還是去向 AppleDev 部門的人反應反應好了

發表於 : 11/01/2004 12:02 pm
janusng
小弟終於認真的測試了那個 sample 程式。

在 XCode 1.5 使用 System version 3.3: gcc (GCC) 3.3 20030304 (Apple Computer, Inc. build 1666) 來 Compile 試試。不論是

代碼: 選擇全部

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[255];
還是

代碼: 選擇全部

char buffer[255];
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
或是

代碼: 選擇全部

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[255];
int wxyz;
還有

代碼: 選擇全部

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[300];
都沒有問題。

再到 Terminal 試過 gcc (3.3) 也一樣行。

看來是閣下的 developer Tools 有問題居多。不如先試試 reinstall Developer Tools。

發表於 : 11/01/2004 12:16 pm
janusng
[off topic]

有關 while(1) 的問題:
1. 如果這是給 OS9 用的程式,我會說『這樣做不恰當,應該要把無限迴圈放在另一個 thread 中,以免把系統鎖死。』
這個還是和 while(1) 沒有關係!在你新開的 thread 中,還不是一樣有 while(1)?
i. 在 OS9 下就不喜歡用 while(1) 來控制,因為在程式開發的時候難免出鎚,一旦一個 break 的條件沒有弄對,結果就很難看。所以我能免則免。
難道將 break 的 condition (a),搬到 while (!a) 便可以保證不出鎚?要是 a 永不 true 或是 while 永不會行第二次的話,怎樣寫也是會 infinite loop 的。 while(1) 或 while(!a) 也沒有不同!

小弟當然也痛恨不少人的壞習慣,或不明就裏的胡亂寫。但問題明顯不在 while(1) 上。

發表於 : 11/01/2004 1:48 pm
ulysses
janusng 寫:看來是閣下的 developer Tools 有問題居多。不如先試試 reinstall Developer Tools。
我有兩台機器,環境都是 10.3.5,安裝 Xcode 1.5,兩台機器都有相同的狀況。當然我在理智上認為問題應該不是出在 address_in 變數怎麼宣告這種不科學的問題。

我試著把所有的 function call 用 errno 檢查,結果問題都是出在 bind:“The specified address is not available from the local machine”。檢查 server_addr.sin_addr 的值始終都是同樣的數值。

所以我是在懷疑,是不是 socket bind 要透過 darwin 底層建立連接的時候,有什麼機制在跟我的 process 搶控制權。
janusng 寫:off topic
off topic 的部份我不再回應了,完全是在雞同鴨講,再扯下去就要翻臉了。

發表於 : 11/01/2004 3:48 pm
藤原佐為
回到這個話題最早的起源:
ulysses 寫到:
brent 寫到:
另外,while(1) 無限迴圈不是好的寫法.....
全看你在什麼環境下開發程式。
好像變成在吵這個了~

我完全認同 ulysses 的看法,我也只是說【 無限迴圈不是好的寫法.....】並不是絕對不能用,特別是這種測試用的小程式,只要能 work,管你喜歡 for 無限還是 while 無限....

只不過在大型程式中還是少用的好,以免 exit 的情況萬一沒發生時,就..... 要是【無限迴圈】用太多,也增加除錯的困難。這點你也知道,當我沒說.....

當然,寫了 while(條件) 也不等於就一定不會造成【無限迴圈】,萬一設定的條件沒發生,還不是一樣!

我有空也裝一下 Xcode 1.5 來跑看看好了。

發表於 : 11/01/2004 4:55 pm
janusng
都是不用吵了,兩位大大是否可以為這個程式,示範一個不會有 while(1) 、while(!a) 或 equivalent(如 for(:: )、do ... while(1))的弊病,而又保留原有功能的寫法呢?

多謝賜教! :D