青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

不會飛的鳥

2010年12月10日 ... 不鳥他們!!! 我要用自己開發的分布式文件系統、分布式調度系統、分布式檢索系統, 做自己的搜索引擎!!!大魚有大志!!! ---楊書童

#

基于TCP協議的網絡程序

下圖是基于TCP協議的客戶端/服務器程序的一般流程:

TCP協議通訊流程

TCP協議通訊流程

服務器調用socket()、bind()、listen()完成初始化后,調用accept()阻塞等待,處于監聽端口的狀態,客戶端調用socket()初始化后,調用connect()發出SYN段并阻塞等待服務器應答,服務器應答一個SYN-ACK段,客戶端收到后從connect()返回,同時應答一個ACK段,服務器收到后從accept()返回。

數據傳輸的過程:

建立連接后,TCP協議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主動發起請求,服務器被動處理請求,一問一答的方式。因此,服務器從accept()返回后立刻調用read(),讀socket就像讀管道一樣,如果沒有數據到達就阻塞等待,這時客戶端調用write()發送請求給服務器,服務器收到后從read()返回,對客戶端的請求進行處理,在此期間客戶端調用read()阻塞等待服務器的應答,服務器調用write()將處理結果發回給客戶端,再次調用read()阻塞等待下一條請求,客戶端收到后從read()返回,發送下一條請求,如此循環下去。

如果客戶端沒有更多的請求了,就調用close()關閉連接,就像寫端關閉的管道一樣,服務器的read()返回0,這樣服務器就知道客戶端關閉了連接,也調用close()關閉連接。注意,任何一方調用close()后,連接的兩個傳輸方向都關閉,不能再發送數據了。如果一方調用shutdown()則連接處于半關閉狀態,仍可接收對方發來的數據。

在學習socket API時要注意應用程序和TCP協議層是如何交互的: *應用程序調用某個socket函數時TCP協議層完成什么動作,比如調用connect()會發出SYN段 *應用程序如何知道TCP協議層的狀態變化,比如從某個阻塞的socket函數返回就表明TCP協議收到了某些段,再比如read()返回0就表明收到了FIN段

最簡單的TCP網絡程序

下面通過最簡單的客戶端/服務器程序的實例來學習socket API。

server.c的作用是從客戶端讀字符,然后將每個字符轉換為大寫并回送給客戶端。

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
    
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	listen(listenfd, 20);

	printf("Accepting connections ...\n");
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = accept(listenfd, 
				(struct sockaddr *)&cliaddr, &cliaddr_len);
	  
		n = read(connfd, buf, MAXLINE);
		printf("received from %s at PORT %d\n",
		       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
		       ntohs(cliaddr.sin_port));
    
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		write(connfd, buf, n);
		close(connfd);
	}
}

下面介紹程序中用到的socket API,這些函數都在sys/socket.h中。

int socket(int family, int type, int protocol);

socket()打開一個網絡通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用read/write在網絡上收發數據,如果socket()調用出錯則返回-1。對于IPv4,family參數指定為AF_INET。對于TCP協議,type參數指定為SOCK_STREAM,表示面向流的傳輸協議。如果是UDP協議,則type參數指定為SOCK_DGRAM,表示面向數據報的傳輸協議。protocol參數的介紹從略,指定為0即可。

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可以向服務器發起連接,因此服務器需要調用bind綁定一個固定的網絡地址和端口號。bind()成功返回0,失敗返回-1。

bind()的作用是將參數sockfd和myaddr綁定在一起,使sockfd這個用于網絡通訊的文件描述符監聽myaddr所描述的地址和端口號。前面講過,struct sockaddr *是一個通用指針類型,myaddr參數實際上可以接受多種協議的sockaddr結構體,而它們的長度各不相同,所以需要第三個參數addrlen指定結構體的長度。我們的程序中對myaddr參數是這樣初始化的:

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

首先將整個結構體清零,然后設置地址類型為AF_INET,網絡地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務器可能有多個網卡,每個網卡也可能綁定多個IP地址,這樣設置可以在所有的IP地址上監聽,直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址,端口號為SERV_PORT,我們定義為8000。

int listen(int sockfd, int backlog);

典型的服務器程序可以同時服務于多個客戶端,當有客戶端發起連接時,服務器調用的accept()返回并接受這個連接,如果有大量的客戶端發起連接而服務器來不及處理,尚未accept的客戶端就處于連接等待狀態,listen()聲明sockfd處于監聽狀態,并且最多允許有backlog個客戶端處于連接待狀態,如果接收到更多的連接請求就忽略。listen()成功返回0,失敗返回-1。

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

三方握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。cliaddr是一個傳出參數,accept()返回時傳出客戶端的地址和端口號。addrlen參數是一個傳入傳出參數(value-result argument),傳入的是調用者提供的緩沖區cliaddr的長度以避免緩沖區溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區)。如果給cliaddr參數傳NULL,表示不關心客戶端的地址。

我們的服務器程序結構是這樣的:

while (1) {
	cliaddr_len = sizeof(cliaddr);
	connfd = accept(listenfd, 
			(struct sockaddr *)&cliaddr, &cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	...
	close(connfd);
}

整個是一個while死循環,每次循環處理一個客戶端連接。由于cliaddr_len是傳入傳出參數,每次調用accept()之前應該重新賦初值。accept()的參數listenfd是先前的監聽文件描述符,而accept()的返回值是另外一個文件描述符connfd,之后與客戶端之間就通過這個connfd通訊,最后關閉connfd斷開連接,而不關閉listenfd,再次回到循環開頭listenfd仍然用作accept的參數。accept()成功返回一個文件描述符,出錯返回-1。

client.c的作用是從命令行參數中獲得一個字符串發給服務器,然后接收服務器返回的字符串并打印。

/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;
	char *str;
    
	if (argc != 2) {
		fputs("usage: ./client message\n", stderr);
		exit(1);
	}
	str = argv[1];
    
	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);
    
	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	write(sockfd, str, strlen(str));

	n = read(sockfd, buf, MAXLINE);
	printf("Response from server:\n");
	write(STDOUT_FILENO, buf, n);

	close(sockfd);
	return 0;
}

由于客戶端不需要固定的端口號,因此不必調用bind(),客戶端的端口號由內核自動分配。注意,客戶端不是不允許調用bind(),只是沒有必要調用bind()固定一個端口號,服務器也不是必須調用bind(),但如果服務器不調用bind(),內核會自動給服務器分配監聽端口,每次啟動服務器時端口號都不一樣,客戶端要連接服務器就會遇到麻煩。

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

客戶端需要調用connect()連接服務器,connect和bind的參數形式一致,區別在于bind的參數是自己的地址,而connect的參數是對方的地址。connect()成功返回0,出錯返回-1。

先編譯運行服務器:

$ ./server
 Accepting connections ...

然后在另一個終端里用netstat命令查看:

$ netstat -apn|grep 8000
 tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN     8148/server

可以看到server程序監聽8000端口,IP地址還沒確定下來。現在編譯運行客戶端:

$ ./client abcd
Response from server:
ABCD

回到server所在的終端,看看server的輸出:

$ ./server
 Accepting connections ...
 received from 127.0.0.1 at PORT 59757

可見客戶端的端口號是自動分配的。現在把客戶端所連接的服務器IP改為其它主機的IP,試試兩臺主機的通訊。

再做一個小實驗,在客戶端的connect()代碼之后插一個while(1);死循環,使客戶端和服務器都處于連接中的狀態,用netstat命令查看:

$ ./server &
[1] 8343
$ Accepting connections ...
./client abcd &
[2] 8344
$ netstat -apn|grep 8000
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN     8343/server         
tcp        0      0 127.0.0.1:44406         127.0.0.1:8000          ESTABLISHED8344/client         
tcp        0      0 127.0.0.1:8000          127.0.0.1:44406         ESTABLISHED8343/server

應用程序中的一個socket文件描述符對應一個socket pair,也就是源地址:源端口號和目的地址:目的端口號,也對應一個TCP連接。

表 37.1. client和server的socket狀態

socket文件描述符 源地址:源端口號 目的地址:目的端口號 狀態
server.c中的listenfd 0.0.0.0:8000 0.0.0.0:* LISTEN
server.c中的connfd 127.0.0.1:8000 127.0.0.1:44406 ESTABLISHED
client.c中的sockfd 127.0.0.1:44406 127.0.0.1:8000 ESTABLISHED

錯誤處理與讀寫控制

上面的例子不僅功能簡單,而且簡單到幾乎沒有什么錯誤處理,我們知道,系統調用不能保證每次都成功,必須進行出錯處理,這樣一方面可以保證程序邏輯正常,另一方面可以迅速得到故障信息。

為使錯誤處理的代碼不影響主程序的可讀性,我們把與socket相關的一些系統函數加上錯誤處理代碼包裝成新的函數,做成一個模塊wrap.c:

#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char *s)
{
	perror(s);
	exit(1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ( (n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (bind(fd, sa, salen) < 0)
		perr_exit("bind error");
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (connect(fd, sa, salen) < 0)
		perr_exit("connect error");
}

void Listen(int fd, int backlog)
{
	if (listen(fd, backlog) < 0)
		perr_exit("listen error");
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ( (n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");
	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

void Close(int fd)
{
	if (close(fd) == -1)
		perr_exit("close error");
}

慢系統調用accept、read和write被信號中斷時應該重試。connect雖然也會阻塞,但是被信號中斷時不能立刻重試。對于accept,如果errno是ECONNABORTED,也應該重試。詳細解釋見參考資料。

TCP協議是面向流的,read和write調用的返回值往往小于參數指定的字節數。對于read調用,如果接收緩沖區中有20字節,請求讀100個字節,就會返回20。對于write調用,如果請求寫100個字節,而發送緩沖區中只有20個字節的空閑位置,那么write會阻塞,直到把100個字節全部交給發送緩沖區才返回,但如果socket文件描述符有O_NONBLOCK標志,則write不阻塞,直接返回20。為避免這些情況干擾主程序的邏輯,確保讀寫我們所請求的字節數,我們實現了兩個包裝函數readn和writen,也放在wrap.c中:

ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;
	ssize_t nread;
	char   *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

如果應用層協議的各字段長度固定,用readn來讀是非常方便的。例如設計一種客戶端上傳文件的協議,規定前12字節表示文件名,超過12字節的文件名截斷,不足12字節的文件名用'\0'補齊,從第13字節開始是文件內容,上傳完所有文件內容后關閉連接,服務器可以先調用readn讀12個字節,根據文件名創建文件,然后在一個循環中調用read讀文件內容并存盤,循環結束的條件是read返回0。

字段長度固定的協議往往不夠靈活,難以適應新的變化。比如,以前DOS的文件名是8字節主文件名加“.”加3字節擴展名,不超過12字節,但是現代操作系統的文件名可以長得多,12字節就不夠用了。那么制定一個新版本的協議規定文件名字段為256字節怎么樣?這樣又造成很大的浪費,因為大多數文件名都很短,需要用大量的'\0'補齊256字節,而且新版本的協議和老版本的程序無法兼容,如果已經有很多人在用老版本的程序了,會造成遵循新協議的程序與老版本程序的互操作性(Interoperability)問題。如果新版本的協議要添加新的字段,比如規定前12字節是文件名,從13到16字節是文件類型說明,從第17字節開始才是文件內容,同樣會造成和老版本的程序無法兼容的問題。

現在重新看看上一節的TFTP協議是如何避免上述問題的:TFTP協議的各字段是可變長的,以'\0'為分隔符,文件名可以任意長,再看blksize等幾個選項字段,TFTP協議并沒有規定從第m字節到第n字節是blksize的值,而是把選項的描述信息“blksize”與它的值“512”一起做成一個可變長的字段,這樣,以后添加新的選項仍然可以和老版本的程序兼容(老版本的程序只要忽略不認識的選項就行了)。

因此,常見的應用層協議都是帶有可變長字段的,字段之間的分隔符用換行的比用'\0'的更常見,例如本節后面要介紹的HTTP協議。可變長字段的協議用readn來讀就很不方便了,為此我們實現一個類似于fgets的readline函數,也放在wrap.c中:

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
	again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;
		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;
	return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) {
			*ptr++ = c;
			if (c  == '\n')
				break;
		} else if (rc == 0) {
			*ptr = 0;
			return n - 1;
		} else
			return -1;
	}
	*ptr  = 0;
	return n;
}

習題 請點評

1、請讀者自己寫出wrap.c的頭文件wrap.h,后面的網絡程序代碼都要用到這個頭文件。

2、修改server.c和client.c,添加錯誤處理。

2.3. 把client改為交互式輸入 請點評

目前實現的client每次運行只能從命令行讀取一個字符串發給服務器,再從服務器收回來,現在我們把它改成交互式的,不斷從終端接受用戶輸入并和server交互。

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;
    
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);
    
	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}

	Close(sockfd);
	return 0;
}

編譯并運行server和client,看看是否達到了你預想的結果。

$ ./client
haha1
HAHA1 
haha2
the other side has been closed.
haha3
$

這時server仍在運行,但是client的運行結果并不正確。原因是什么呢?仔細查看server.c可以發現,server對每個請求只處理一次,應答后就關閉連接,client不能繼續使用這個連接發送數據。但是client下次循環時又調用write發數據給server,write調用只負責把數據交給TCP發送緩沖區就可以成功返回了,所以不會出錯,而server收到數據后應答一個RST段,client收到RST段后無法立刻通知應用層,只把這個狀態保存在TCP協議層。client下次循環又調用write發數據給server,由于TCP協議層已經處于RST狀態了,因此不會將數據發出,而是發一個SIGPIPE信號給應用層,SIGPIPE信號的缺省處理動作是終止程序,所以看到上面的現象。

為了避免client異常退出,上面的代碼應該在判斷對方關閉了連接后break出循環,而不是繼續write。另外,有時候代碼中需要連續多次調用write,可能還來不及調用read得知對方已關閉了連接就被SIGPIPE信號終止掉了,這就需要在初始化時調用sigaction處理SIGPIPE信號,如果SIGPIPE信號沒有導致進程異常退出,write返回-1并且errno為EPIPE。

另外,我們需要修改server,使它可以多次處理同一客戶端的請求。

/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
    
	Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	Listen(listenfd, 20);

	printf("Accepting connections ...\n");
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = Accept(listenfd, 
				(struct sockaddr *)&cliaddr, &cliaddr_len);
		while (1) {
			n = Read(connfd, buf, MAXLINE);
			if (n == 0) {
				printf("the other side has been closed.\n");
				break;
			}
			printf("received from %s at PORT %d\n",
			       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
			       ntohs(cliaddr.sin_port));
    
			for (i = 0; i < n; i++)
				buf[i] = toupper(buf[i]);
			Write(connfd, buf, n);
		}
		Close(connfd);
	}
}

經過上面的修改后,客戶端和服務器可以進行多次交互了。我們知道,服務器通常是要同時服務多個客戶端的,運行上面的server和client之后,再開一個終端運行client試試,新的client能得到服務嗎?想想為什么。

2.4. 使用fork并發處理多個client的請求 請點評

怎么解決這個問題?網絡服務器通常用fork來同時服務多個客戶端,父進程專門負責監聽端口,每次accept一個新的客戶端連接就fork出一個子進程專門服務這個客戶端。但是子進程退出時會產生僵尸進程,父進程要注意處理SIGCHLD信號和調用wait清理僵尸進程。

以下給出代碼框架,完整的代碼請讀者自己完成。

listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, ...); 
while (1) {
	connfd = accept(listenfd, ...);
	n = fork();
	if (n == -1) {
		perror("call to fork");
		exit(1);
	} else if (n == 0) {
		close(listenfd);
		while (1) {
			read(connfd, ...);
			...
			write(connfd, ...);
		}
		close(connfd);
		exit(0);
	} else
		close(connfd);
}

2.5. setsockopt 請點評

現在做一個測試,首先啟動server,然后啟動client,然后用Ctrl-C使server終止,這時馬上再運行server,結果是:

$ ./server
 bind error: Address already in use

這是因為,雖然server的應用程序終止了,但TCP協議層的連接并沒有完全斷開,因此不能再次監聽同樣的server端口。我們用netstat命令查看一下:

$ netstat -apn |grep 8000
 tcp        1      0 127.0.0.1:33498         127.0.0.1:8000          CLOSE_WAIT 10830/client        
 tcp        0      0 127.0.0.1:8000          127.0.0.1:33498         FIN_WAIT2  -

server終止時,socket描述符會自動關閉并發FIN段給client,client收到FIN后處于CLOSE_WAIT狀態,但是client并沒有終止,也沒有關閉socket描述符,因此不會發FIN給server,因此server的TCP連接處于FIN_WAIT2狀態。

現在用Ctrl-C把client也終止掉,再觀察現象:

$ netstat -apn |grep 8000
 tcp        0      0 127.0.0.1:8000          127.0.0.1:44685         TIME_WAIT  -
 $ ./server
 bind error: Address already in use

client終止時自動關閉socket描述符,server的TCP連接收到client發的FIN段后處于TIME_WAIT狀態。TCP協議規定,主動關閉連接的一方要處于TIME_WAIT狀態,等待兩個MSL(maximum segment lifetime)的時間后才能回到CLOSED狀態,因為我們先Ctrl-C終止了server,所以server是主動關閉連接的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server端口。MSL在RFC1122中規定為兩分鐘,但是各操作系統的實現不同,在Linux上一般經過半分鐘后就可以再次啟動server了。至于為什么要規定TIME_WAIT的時間請讀者參考UNP 2.7節。

在server的TCP連接沒有完全斷開之前不允許重新監聽是不合理的,因為,TCP連接沒有完全斷開指的是connfd(127.0.0.1:8000)沒有完全斷開,而我們重新監聽的是listenfd(0.0.0.0:8000),雖然是占用同一個端口,但IP地址不同,connfd對應的是與某個客戶端通訊的一個具體的IP地址,而listenfd對應的是wildcard address。解決這個問題的方法是使用setsockopt()設置socket描述符的選項SO_REUSEADDR為1,表示允許創建端口號相同但IP地址不同的多個socket描述符。在server代碼的socket()和bind()調用之間插入如下代碼:

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

有關setsockopt可以設置的其它選項請參考UNP第7章。

2.6. 使用select 請點評

select是網絡程序中很常用的一個系統調用,它可以同時監聽多個阻塞的文件描述符(例如多個網絡連接),哪個有數據到達就處理哪個,這樣,不需要fork和多進程就可以實現并發服務的server。

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char **argv)
{
	int i, maxi, maxfd, listenfd, connfd, sockfd;
	int nready, client[FD_SETSIZE];
	ssize_t n;
	fd_set rset, allset;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	socklen_t cliaddr_len;
	struct sockaddr_in	cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	Listen(listenfd, 20);

	maxfd = listenfd;		/* initialize */
	maxi = -1;			/* index into client[] array */
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;	/* -1 indicates available entry */
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);

	for ( ; ; ) {
		rset = allset;	/* structure assignment */
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if (nready < 0)
			perr_exit("select error");

		if (FD_ISSET(listenfd, &rset)) { /* new client connection */
			cliaddr_len = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

			printf("received from %s at PORT %d\n",
			       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
			       ntohs(cliaddr.sin_port));

			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd; /* save descriptor */
					break;
				}
			if (i == FD_SETSIZE) {
				fputs("too many clients\n", stderr);
				exit(1);
			}

			FD_SET(connfd, &allset);	/* add new descriptor to set */
			if (connfd > maxfd)
				maxfd = connfd; /* for select */
			if (i > maxi)
				maxi = i;	/* max index in client[] array */

			if (--nready == 0)
				continue;	/* no more readable descriptors */
		}

		for (i = 0; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
					/* connection closed by client */
					Close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else {
					int j;
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Write(sockfd, buf, n);
				}

				if (--nready == 0)
					break;	/* no more readable descriptors */
			}
		}
	}
}

posted @ 2011-07-03 13:59 不會飛的鳥 閱讀(306) | 評論 (0)編輯 收藏

基于UDP協議的網絡程序

 

下圖是典型的UDP客戶端/服務器通訊過程。

 UDP通訊流程

UDP通訊流程

以下是簡單的UDP服務器和客戶端程序。

/* server.c */
#include 
<stdio.h>
#include 
<string.h>
#include 
<netinet/in.h>
#include 
"wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
    
struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    
int sockfd;
    
char buf[MAXLINE];
    
char str[INET_ADDRSTRLEN];
    
int i, n;

    sockfd 
= Socket(AF_INET, SOCK_DGRAM, 0);

    bzero(
&servaddr, sizeof(servaddr));
    servaddr.sin_family 
= AF_INET;
    servaddr.sin_addr.s_addr 
= htonl(INADDR_ANY);
    servaddr.sin_port 
= htons(SERV_PORT);
    
    Bind(sockfd, (
struct sockaddr *)&servaddr, sizeof(servaddr));

    printf(
"Accepting connections \n");
    
while (1{
        cliaddr_len 
= sizeof(cliaddr);
        n 
= recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
        
if (n == -1)
            perr_exit(
"recvfrom error");
        printf(
"received from %s at PORT %d\n",
               inet_ntop(AF_INET, 
&cliaddr.sin_addr, str, sizeof(str)),
               ntohs(cliaddr.sin_port));
    
        
for (i = 0; i < n; i++)
            buf[i] 
= toupper(buf[i]);
        n 
= sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
        
if (n == -1)
            perr_exit(
"sendto error");
    }

}

/* client.c */
#include 
<stdio.h>
#include 
<string.h>
#include 
<unistd.h>
#include 
<netinet/in.h>
#include 
"wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    
struct sockaddr_in servaddr;
    
int sockfd, n;
    
char buf[MAXLINE];
    
char str[INET_ADDRSTRLEN];
    socklen_t servaddr_len;
    
    sockfd 
= Socket(AF_INET, SOCK_DGRAM, 0);

    bzero(
&servaddr, sizeof(servaddr));
    servaddr.sin_family 
= AF_INET;
    inet_pton(AF_INET, 
"127.0.0.1"&servaddr.sin_addr);
    servaddr.sin_port 
= htons(SERV_PORT);
    
    
while (fgets(buf, MAXLINE, stdin) != NULL) {
        n 
= sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
        
if (n == -1)
            perr_exit(
"sendto error");

        n 
= recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
        
if (n == -1)
            perr_exit(
"recvfrom error");
      
        Write(STDOUT_FILENO, buf, n);
    }


    Close(sockfd);
    
return 0;
}

由于UDP不需要維護連接,程序邏輯簡單了很多,但是UDP協議是不可靠的,實際上有很多保證通訊可靠性的機制需要在應用層實現。

編譯運行server,在兩個終端里各開一個client與server交互,看看server是否具有并發服務的能力。用Ctrl+C關閉server,然后再運行server,看此時client還能否和server聯系上。和前面TCP程序的運行結果相比較,體會無連接的含義。

posted @ 2011-07-03 13:56 不會飛的鳥 閱讀(252) | 評論 (0)編輯 收藏

線性查找和二分查找

問題:數組查找它和數組排序一樣是重要的計算應用之一,電話公司根據姓氏查找,能容易的找到用戶的電話號碼和繳費情況,在學校成績管理系統可以根據學生的學號,很容易就能查找到學生的成績及相關資料,查找在生活中的應用是十分廣泛,數據排序是一個令人感興趣的問題,這里深入理解兩種最基本的算法:線型查找和二分法查找。

         線型查找:把數組的每一個元素和檢索關鍵字比較,安順序從第一個元素一直檢索到要查找的元素,平均來說,程序要把查找關鍵字與一半數組元素進行比較。二分法查找:線型查找法對小型數組和未排序的數組效果較好,但是,對于大型數據來說,線型查找法效率較低。如果已經對數組排序,那么可以使用速度很快的二分法查找.

程序1:線型查找法實現對某個數的查找!
#include<stdio.h>
#include<stdlib.h>
#define Size 100
int         main()   
{
            int linearSearch(int a[],int key,int size);
            int         a[Size],i,searchKey,element;
            for(i=0;i<Size-1;i++)
            a[i]=2*i;
            printf("Enter integer search key:\n");
            scanf("%d",&searchKey);
            element=linearSearch(a,searchKey,Size);
            if(element!=-1)
                printf("Found value in element %d !\n",element);
            else
                printf("Value is not found!\n");
            system("pause");
}   

int linearSearch(int array[],int key,int size)
{
           int j;
           for(j=0;j<Size-1;j++)
           if(array[j]==key)
              return j;
           return -1;
}

程序2:二分法查找法實現對某個數的查找!
#include<stdio.h>
#include<stdlib.h>
#define Size 15
int         main()   
{
            int binarySearch(int [],int,int,int);
            void printHeader(void);
            void printRow(int [],int,int,int);
            int a[Size],i,key,element;
            for(i=0;i<=Size-1;i++)
                a[i]=2*i;
            printf("Enter a number between 0 and 28:");
            scanf("%d",&key);
            printHeader();
            element=binarySearch(a,key,0,Size-1);
            if(element!=-1)
                printf("\n%d found in array element %d !\n",key,element);
            else
                printf("\n%d is not found!\n",key);
         
            system("pause");
}  

void printHeader()
{
            int i;
            printf("\nSubscripts:\n");
            for(i=0;i<=Size-1;i++)
                printf("%3d",i);
            printf("\n");
            for(i=1;i<=4*Size;i++)
                printf("-");
            printf("\n");
}

int binarySearch(int array[],int searchKey,int low,int high)
{
           void printRow(int array[],int low,int middle,int high);
           int middle;
           while(low<=high)
           {
               middle=(low+high)/2;
               printRow(array,low,middle,high);
               if(searchKey==array[middle])
                  return middle;
               else if(searchKey<array[middle])
                  high=middle-1;
               else
                  low=middle+1;
           }
           return -1;
}
void printRow(int array[],int low,int middle,int high)
{
            int i;
            for(i=0;i<=Size-1;i++)
                if(i<low||i>high)
                   printf(" ");
                else if(i==middle)
                   printf("%3d*",array[i]);
                else
                   printf("%3d",array[i]);
            printf("\n");
}


           效率分析:線型查找擺脫了數組排序的約束,不足之處是不適合大型數據查找,并且查找方法比較老套,如果要找的數在數組中最后一個數n,那么搜索從0開始,一直檢索到n,要經過n次遍歷,時間復雜度:O(n),而二分查找法中如果查找關鍵字小于數組中間的元素,就查找數組的頭半部分,否則查找數組的后半部分,時間復雜度:O(log2n),如果在指定子數組中還沒有查找到關鍵字,就再把子數組折半,反復進行這種查找,直到要查找的關鍵字等于子數組中間的元素,或沒有找到關鍵字為止。在最壞的情況下,用二分法查找有1024個元素的數組也只需要比較10次,即用2除1024,連續除10次得到1為止,如果有1048576(2的20次方)個元素,用二分法只要比較20次就可以找到要查找的元素,而用簡單的線型查找則需要進行2的20次方查找,可見二分法比線型查找法的效率要高得多,對10億哥元素的數組來說,平均比較5億次和30次簡直是天壤之別!所以掌握二分法對在龐大的數組庫處理是很有效的!

posted @ 2011-06-27 13:48 不會飛的鳥 閱讀(1740) | 評論 (0)編輯 收藏

數組排序的集中方法

問題:數組排序(即按某種特定的順序排列數據,如升序或降序)是最重要的計算應用之一,銀行用帳號對所有的支票進行能夠排序,并根據排序結果準備月底的財務報告,學校學生成績管理系統用數組排序的方法將考試成績從高到低進行排名,數組排序方法很多,有直接插入排序、冒泡排序、快速排序、直接選擇排序,下面來詳細介紹這四種基本的排序方法及其實現。

1,直接插入排序:當數據表A中每個元素距其最終位置不遠,數據表A按關鍵字值基本有序,可用此方法排序較快。

2,冒泡排序法:將較小的值“上浮”到數組頂部,而較大值“下沉”到數組底部,這種排序技術要比較好幾趟,每一趟要比較連續的數組元素對,如果某對數值是按升序排序的(或者這兩個值相等),那就保持原樣,如果某對數組是按降序排列的,就要交換它們的值。

3,快速排序法:快速排序是對冒泡排序的一種改進。它的基本思想是:通過一躺排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按次方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

4,直接選擇排序法:直接選擇排序的作法是:第一趟掃描所有數據,選擇其中最小的一個與第一個數據互換;第二趟從第二個數據開始向后掃描,選擇最小的與第二個數據互換;依次進行下去,進行了(n-1)趟掃描以后就完成了整個排序過程。它比起冒泡排序有一個優點就是不用不斷的交換。

程序1:直接插入法實現對數組的排序!
#include<stdio.h>
#include<conio.h>

int main()
{
        void InsertSort(int [],int);
        int a[7]={8,10,2,3,1,7,13};
        int i;
        InsertSort(a,7);
        for(i=0;i<7;i++)
           printf("%4d",a[i]);
        getch();
}
void InsertSort(int a[],int count)
{
        int i,j,temp;
        for(i=1;i<count;i++)   
        {
           temp=a[i];
           j=i-1;
           while(a[j]>temp && j>=0)
           {
             a[j+1]=a[j];
              j--;
           }
           if(j!=(i-1))     
             a[j+1]=temp;
         }
}

程序2:冒泡法實現對數組的排序!
#include<stdio.h>
#include<conio.h>
int main()
{
         void BubbleSort(int []);
         int a[10];
         int i,j,temp;
         printf("Input tem integer numbers for a[10]:");
         for(i=0;i<10;i++)
            scanf("%d,",&a[i]);
         printf("\n");
         BubbleSort(a);
         printf("The sorted array is:\n");
            for(j=0;j<10;j++)
                 printf("%d,",a[j]);
         printf("\n\n");
         getch();
}

void BubbleSort(int array[])
{
         int i,j,temp;
           for(j=0;j<9;j++)
              for(i=0;i<9-j;i++)
                 if(array[i]>array[i+1])
                  {
                      temp=array[i];
                      array[i]=array[i+1];
                      array[i+1]=temp;
                   }
}

程序3:快速排序法實現對數組的排序!
#include<stdio.h>
#include<conio.h>
#define Max 8

int main()
{
         void QuickSort(int a[],int p,int r);
         int a[]={2,8,7,1,3,5,6,4};
         QuickSort(a,1,Max);
         printf(" The sorted array is :");
            for(int i=0;i<Max;i++)
               printf("%d,",a[i]);
         printf("\n");
         getch();
}

void QuickSort(int a[],int p,int r)
{
         int Partition(int a[],int p,int r);
         if(p<r)
         {
            int q=Partition(a,p,r);
            QuickSort(a,p,q-1);
            QuickSort(a,q+1,r);
         }
}

int Partition(int a[],int p,int r)
{
         int i=p-1;
         int x=a[r-1];
            for(int j=p;j<r;j++)
            {
               if(a[j-1]<=x)
                {
                   i=i+1;
                   int temp;
                   temp=a[j-1];
                   a[j-1]=a[i-1];
                   a[i-1]=temp;
                 }
             }
         int temp;
         temp=a[i];
         a[i]=a[r-1];
         a[r-1]=temp;
         return i+1;
}


程序4:直接選擇法實現對數組的排序!

#include<stdio.h>
#include<conio.h>

int main()
{
         void ChooseSort(int []);
         int i,j,a[10];
         printf("Input ten integer numbers for a[10]: ");
            for(i=0;i<10;i++)
               scanf("%d,",&a[i]);
         printf("\n");
         ChooseSort(a);
         printf("The sorted array is:\n");
            for(j=0;j<10;j++)
               printf("%d,",a[j]);
         printf("\n\n");
         getch();
}

void ChooseSort(int array[])
{
         int j,temp,*p1,*p2;
         for(p1=array;p1<array+9;p1++)
            {
              j++;
              for(p2=array+j;p2<=array+9;p2++)
                if(*p2<*p1)
                  {
                     temp=*p2;
                     *p2=*p1;
                     *p1=temp;
                  }
            }
}

各種排序方法的綜合比較:

一、時間性能

按平均的時間性能來分,四種類排序方法時間復雜度分別為:
直接插入排序法:O(n^2),冒泡排序法:O(n^2)快速排序法:O(nlogn),直接選擇排序法:O(n^2)
時間復雜度為O(n^2)的有:直接插入排序、起泡排序和簡單選擇排序,其中以直接插入為最好,特別是對那些對關鍵字近似有序的記錄序列尤為如此;當待排記錄序列按關鍵字順序有序時,直接插入排序和起泡排序能達到O(n)的時間復雜度;而對于快速排序而言,這是最不好的情況,此時的時間性能蛻化為O(n2),因此是應該盡量避免的情況。

二、排序方法的穩定性能

1. 穩定的排序方法指的是,對于兩個關鍵字相等的記錄,它們在序列中的相對位置,在排序之前和經過排序之后,沒有改變。
2. 當對多關鍵字的記錄序列進行LSD方法排序時,必須采用穩定的排序方法。
3. 對于不穩定的排序方法,只要能舉出一個實例說明即可。
4. 快速排序是不穩定的排序方法。

posted @ 2011-06-27 13:47 不會飛的鳥 閱讀(371) | 評論 (0)編輯 收藏

linux sock_raw原始套接字編程

     摘要: sock_raw原始套接字編程可以接收到本機網卡上的數據幀或者數據包,對與監聽網絡的流量和分析是很有作用的.一共可以有3種方式創建這種socket   1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)發送接收ip數據包 2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P...  閱讀全文

posted @ 2011-06-27 09:16 不會飛的鳥 閱讀(697) | 評論 (0)編輯 收藏

struct ethhdr、ether_header、iphdr、tcphdr、udphdr 結構

************************eth的結構**************************************

struct ethhdr {
unsigned char h_dest[ETH_ALEN];
unsigned char h_source[ETH_ALEN];
__be16 h_proto;
} __attribute__((packed));

struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN];      // destination eth addr
u_int8_t ether_shost[ETH_ALEN];      // source ether addr   
u_int16_t ether_type;                 // packet type ID field
} __attribute__ ((__packed__));

***********************IP的結構***********************************
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
};

***********************TCP的結構****************************
struct tcphdr
{
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
# else
#   error "Adjust your <bits/endian.h> defines"
# endif
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_ptr;
};
***********************UDP的結構*****************************
struct udphdr
{
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};

posted @ 2011-06-27 09:15 不會飛的鳥 閱讀(6729) | 評論 (0)編輯 收藏

[VS2005]解決“由于應用程序的配置不正確,應用程序未能啟動,重新安裝應用程序可能會糾正這個問題”

今天在準備發布用VS2005寫的那個程序時,拷貝到我同事機器上,雙擊突然出現了“由于應用程序的配置不正確,應用程序未能啟動,重新安裝應用程序可能會糾正這個問題“,這個問題很讓我意外,以前只出現過缺少DLL的情況,而這次出現這個問題,讓我一時沒辦法。想想,無非是兩個原因引起的,要么是他沒有安裝VS2005的原因,要么是我的程序里依賴了其它的一些庫。于是百度一下,發現好多相關主題。我是按照這個帖子解決的:

在VS2005下用C++寫的程序,在一臺未安裝VS2005的系統上,
用命令行方式運行,提示:
“系統無法執行指定的程序”
直接雙擊運行,提示:
“由于應用程序的配置不正確,應用程序未能啟動,重新安裝應用程序可能會糾正這個問題”

以前用VC6和VS2003的話, 如果缺少庫文件,是會提示缺少“**.dll”,但是用VS2005卻沒有這樣的提示。

自己實驗了一下,感覺以下幾種解決辦法是可行的:
方法一:
在類似C:\Program Files\Microsoft Visual Studio 8\VC\redi
st\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT 下找到了下列文件:

msvcm80d.dll
msvcp80d.dll
msvcr80d.dll
Microsoft.VC80.DebugCRT.manifest

把這幾個文件拷貝到目標機器上,與運行程序同一文件夾或放到system32下,就可以正確運行了。

其他release版、MFC程序什么的都是拷redist下相應文件夾下的文件就可以了,文件夾后都有標識!

方法二:
修改編譯選項,將/MD或/MDd 改為 /MT或/MTd,這樣就實現了對VC運行時庫的靜態鏈接,在運行時就不再需要VC的dll了。

方法三:

工程-》屬性-》配置屬性-》常規-》MFC的使用,選擇“在靜態庫中使用mfc”
這樣生成的exe文件應該就可以在其他機器上跑了。

方法四:

你的vc8安裝盤上找到再分發包vcredist_xxx.exe和你的程序捆綁安裝

 

C#調用c++制作的DLL時,一些參數的賦值問題如char *,結構體

c++ dll中的原型
int test(char* xm,char* fa,UINT &VerNum,double Mile,char *SurvMile);
c#調用時
 [DllImport(@"Test2.DLL")]
public static extern int test(string xm,string fa,ref UInt32 VerNum,double Mile, StringBuilder SurvMile);

 注意:
1.調用的時候,有部分char* ,如果想獲得返回值,不能用string 作參數來進行調用,這樣得不到返回到結果,可以用StringBuilder來聲明變 
 StringBuilder   strMyTemp  =   new   StringBuilder(256);//256是長度
2.結構體的引用傳遞
  首先在c#中定義和c++相同的結構體,如果是引用傳遞,在結構體前面加上[In, Out]
     [DllImport(@"test.dll")]
     public static extern int test([In, Out] SLineData[] lndt,ref UInt32 length);

3.其他的類型如整形等等用ref加上數據變量則可獲得返回值

 

使用C++調用C#的DLL

SwfDotNet是C#編寫的,作者的C#水平,真是令我佩服。這是個特別好的讀寫Swf文件的庫。但是,我要用在C++項目中,怎么讓C++調用C#的DLL呢。今天一上午都在琢磨這個問題,耽誤了很多時間,原因是編譯是出現:
warning C4819: 該文件包含不能在當前代碼頁(936)中表示的字符。請將該文件保存為 Unicode 格式以防止數據丟失。
接著就是一大堆的0x01等等。自己做了個Sample,仔細分析發現還是自己沒有搞清楚。正確的操作如下:
1 創建C# DLL,需要指定應用類型為“類庫”,代碼:
namespace CSLib
{
    public class Class1
    {
        private string name;

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = "Your Name: " + value;
            }
        }
    }
}

2 C++客戶程序,是個控制臺應用,代碼:
#using "..\debug\CSLib.dll"
using namespace CSLib;

int _tmain(int argc, _TCHAR* argv[])
{
 Class1 ^c = gcnew Class1();

 c->Name = "zzj";

 printf("%s\n", c->Name);

 return 0;
}

3 幾點要記住:
 1 使用#using引用C# DLL,而不是#include。我就是想當然的使用了后者,所以浪費了一上午的時間;
 2 別忘了using namespace CSLib;
 3 使用C++/clr語法,采用正確的訪問托管對象,即:使用帽子'^',而不是星星'*'(選擇菜單[項目]->[屬性],在其[屬性頁]中的[公共語言運行庫支持]項)


posted @ 2010-07-06 14:34 不會飛的鳥 閱讀(1298) | 評論 (1)編輯 收藏

PD(PowerDesigner) 常見的一些sql腳本生成配置

1、去掉Oracle生成的SQL創建語句中的雙引號
用powerdesigner導出orale數據庫的建表sql時,默認會給表名和字段名加上雙引號,如下圖:

這樣給操作數據庫帶來很大的不便,解決的辦法是設置Database菜單,

然后點擊Edit Current DBMS菜單,再依次點開Script->Format,然后找到CaseSensitivityUsingQuote
將其設為NO,即可。如下圖:

如果帶有包的話,導出時要選擇包中的表。

2、PowerDesign高級應用
編寫相關的VBS腳本在PowerDesign里自定義一些命令與操作等,具體的可以參考C:\Program Files\Sybase\PowerDesigner 9\VB Scripts目錄下的腳本示例。怎么運用這些腳本呢?
在Tools->Execute Commands里可以進行操作。具體說明在幫助里寫的很清楚。幫助的位置在 PowerDesigner General Features Guide-> PART 2. Modeling Guide->CHAPTER 8. Managing Objects->Accessing objects using VBScript->VBScript uses in PowerDesigner

PowerDesign的使用主要是DBMS的配置
3、修改建表腳本生成規則。
如果每個表格都有相同的字段,可以如下修改:
Database -> Edit Current DBMS 展開 Script -> Object -> Table -> Create 見右下的Value值,可以直接修改如下:

/* tablename: %TNAME% */
create table [%QUALIFIER%]%TABLE% (
   %TABLDEFN%
   ts                   char(19)             null default convert(char(19),getdate(),20),
   dr                   smallint             null default 0
)
[%OPTIONS%]

其中的 ts、dr 兩列會在生成SQL腳本的時候自動的插入每個表格中,其中的%TNAME% 變量是給每個表格的SQL添加一個該表的Name值注釋。

4、修改字段生成規則
要給每個字段都添加一個注釋的話,同一窗口中展開 Script -> Object -> Column -> Add 的 Value修改為:

%20:COLUMN% [%COMPUTE%?AS (%COMPUTE%):%20:DATATYPE% [%IDENTITY%?%IDENTITY%:[%NULL%][%NOTNULL%]][ default %DEFAULT%]
     [[constraint %CONSTNAME%] check (%CONSTRAINT%)]]/*%COLNNAME%*/

其中的%COLNNAME%就是列的Name值(可以是中文)

5、修改外鍵命名規則。
選擇Database—>Edit Current DBMS
選擇Scripts-》Objects-》Reference-》ConstName
可以發現右側的Value為:

FK_%.U8:CHILD%_%.U9:REFR%_%.U8:PARENT%

可見,該命名方法是:'FK_'+8位子表名+9位Reference名+8位父表名,你可以根據這中模式自定義為:

FK_%.U7:CHILD%_RELATIONS_%.U7:PARENT%,

可以使FK名稱變為FK_TABLE_2_RELATIONS_TABLE_1
掌握這種方法后就可以按照自己的想法修改了

生成建庫腳本SQL文件中的表頭注釋很討厭,可以在 Databse -> Generate Database (Ctrl+G)窗口中,選擇Options卡片,去掉Usage的Title鉤選項即可。

6、添加外鍵
Model -> References新建一條外鍵后,雙擊進入外鍵屬性,在“Joins”卡片中可以選擇子表的外鍵字段。如下圖:

接著出現如下畫面:

按照步驟操作即可。

7、取消name和code聯動
在修改name的時候,code的值將跟著變動,很不方便。修改方法:PowerDesign中的選項菜單里修改,在[Tool]-->[General Options]->[Dialog]->[Operating modes]->[Name to Code mirroring],這里默認是讓名稱和代碼同步,將前面的復選框去掉就行了。如圖:



編寫相關的VBS腳本在PowerDesign里自定義一些命令與操作等,具體的可以參考C:\Program Files\Sybase\PowerDesigner 9\VB Scripts目錄下的腳本示例。怎么運用這些腳本呢?
在Tools-》Execute Commands里可以進行操作。具體說明在幫助里寫的很清楚。幫助的位置在 PowerDesigner General Features Guide-> PART 2. Modeling Guide->CHAPTER 8. Managing Objects->Accessing objects using VBScript->VBScript uses in PowerDesigner

PowerDesign的使用主要是DBMS的配置
1、修改建表腳本生成規則。如果每個表格都有相同的字段,可以如下修改:
Database -> Edit Current DBMS 展開 Script -> Object -> Table -> Create 見右下的Value值,可以直接修改如下:

/* tablename: %TNAME% */
create table [%QUALIFIER%]%TABLE% (
   %TABLDEFN%
   ts                   char(19)             null default convert(char(19),getdate(),20),
   dr                   smallint             null default 0
)
[%OPTIONS%]

其中的 ts、dr 兩列會在生成SQL腳本的時候自動的插入每個表格中,其中的%TNAME% 變量是給每個表格的SQL添加一個該表的Name值注釋。

2、修改字段生成規則。要給每個字段都添加一個注釋的話,同一窗口中展開 Script -> Object -> Column -> Add 的 Value修改為:

%20:COLUMN% [%COMPUTE%?AS (%COMPUTE%):%20:DATATYPE% [%IDENTITY%?%IDENTITY%:[%NULL%][%NOTNULL%]][ default %DEFAULT%]
     [[constraint %CONSTNAME%] check (%CONSTRAINT%)]]/*%COLNNAME%*/

其中的%COLNNAME%就是列的Name值(可以是中文)

3、修改外鍵命名規則。選擇Database—>Edit Current DBMS
選擇Scripts-》Objects-》Reference-》ConstName
可以發現右側的Value為:

FK_%.U8:CHILD%_%.U9:REFR%_%.U8:PARENT%

可見,該命名方法是:'FK_'+8位子表名+9位Reference名+8位父表名,你可以根據這中模式自定義為:

FK_%.U7:CHILD%_RELATIONS_%.U7:PARENT%,

可以使FK名稱變為FK_TABLE_2_RELATIONS_TABLE_1
掌握這種方法后就可以按照自己的想法修改了

生成建庫腳本SQL文件中的表頭注釋很討厭,可以在 Databse -> Generate Database (Ctrl+G)窗口中,選擇Options卡片,去掉Usage的Title鉤選項即可。

4、添加外鍵
Model -> References新建一條外鍵后,雙擊進入外鍵屬性,在“Joins”卡片中可以選擇子表的外鍵字段

5、去掉生成的SQL腳本雙引號的問題:ORACLE 8I2::Script\Sql\Format\CaseSensitivityUsingQuote改成No,默認是Yes所以會有雙引號。

在修改name的時候,code的值將跟著變動,很不方便。修改方法:PowerDesign中的選項菜單里修改,在[Tool]-->[General Options]->[Dialog]->[Operating modes]->[Name to Code mirroring],這里默認是讓名稱和代碼同步,將前面的復選框去掉就行了。  

posted @ 2010-07-05 16:14 不會飛的鳥 閱讀(1345) | 評論 (0)編輯 收藏

如何解決ORA-00054資源正忙,要求指定NOWAIT?

問題描述:
我在oem下執行一些操作時,有時碰到如下信息:
如何解決ORA-00054資源正忙,要求指定NOWAIT,
查閱錯誤代碼指南后有如下提示:
ORA-00054 resource busy and acquire with NOWAIT specified
Cause: The NOWAIT keyword forced a return to the command prompt because a resource was unavailable for a LOCK TABLE or SELECT FOR UPDATE command.


解決方法:
1.通過上句查找出已被鎖定的數據庫表及相關的sid、serial#及spid
    select object_name as 對象名稱,s.sid,s.serial#,p.spid as 系統進程號
    from v$locked_object l , dba_objects o , v$session s , v$process p
    where l.object_id=o.object_id and l.session_id=s.sid and s.paddr=p.addr;

2.在數據庫中滅掉相關session
    alter system kill session 'sid,serial#';--sid及serial#為第一步查出來的數據

舉例:
select object_name as 對象名稱,s.sid,s.serial#,p.spid as 系統進程號
    from v$locked_object l , dba_objects o , v$session s , v$process p
    where l.object_id=o.object_id and l.session_id=s.sid and s.paddr=p.addr;


alter system kill session '125,287'

posted @ 2010-06-08 16:54 不會飛的鳥 閱讀(1103) | 評論 (0)編輯 收藏

nm 命令

上次調試一個程序。程序在使用dlopen()出現錯誤。錯誤信息是加載動態庫時,動態庫中有未定義的符號。明明知道是動態庫的問題,可是自己找了半天,沒解決問題。張博士過來,一個ldd查看一下動態庫就判斷庫有問題,再一個nm就找到錯誤的地方,然后重新ld了一下庫,問題解決了。神奇了!我也用這幾個命令檢查過庫,就是沒發現問題,還是對命令的用法不熟啊。
這里把這幾個命令好好整理一下,也算幫助記憶吧。(翻譯的成居多,hehe)

nm命令
這個命令列出目標文件的符號。如果沒有指定目標文件,默認是a.out。
命令大綱
nm [-a|--debug-syms] [-g|--extern-only]
   [-B] [-C|--demangle[=style]] [-D|--dynamic]
   [-S|--print-size] [-s|--print-armap]
   [-A|-o|--print-file-name]
   [-n|-v|--numeric-sort] [-p|--no-sort]
   [-r|--reverse-sort] [--size-sort] [-u|--undefined-only]
   [-t radix|--radix=radix] [-P|--portability]
   [--target=bfdname] [-fformat|--format=format]
   [--defined-only] [-l|--line-numbers] [--no-demangle]
   [-V|--version] [-X 32_64] [--help]  [objfile...] 
輸出格式
nm命令的輸出包含三個部分:1 符號值。默認顯示十六進制,也可以指定; 2 符號類型。小寫表示是本地符號,大寫表示全局符號(external); 3 符號名稱。 給個例子:
08049ad8 A __bss_start
080485e8 t call_gmon_start
08049ad8 b completed.1

下面把符號類型介紹一下(我就是不熟悉符號類型,才對錯誤沒這么敏感)
A
符號值是絕對的。在進一步的連接中,不會被改變。
B
符號位于未初始化數據段(known as BSS).
C
共用(common)符號. 共用符號是未初始化的數據。在連接時,多個共用符號可能采用一個同樣的名字,如果這個符號在某個地方被定義,共用符號被認為是未定義的引用.
D
已初始化數據段的符號
G
已初始化數據段中的小目標(small objective)符號. 一些目標文件格式允許更有效的訪問小目標數據,比如一個全局的int變量相對于一個大的全局數組。
I
其他符號的直接應用,這是GNU擴展的,很少用了.
N
調試符號.
R
只讀數據段符號.
S
未初始化數據段中的小目標(small object)符號.
T
代碼段的符號.
U
未定義符號.
V
弱對象(weak object)符號. 當一個已定義的弱符號被連接到一個普通定義符號,普通定義符號可以正常使用,當一個未定義的弱對象被連接到一個未定義的符號,弱符號的值為0.
W
一個沒有被指定一個弱對象符號的弱符號(weak symbol)。
-
a.out目標文件中的刺符號(stabs symbol). 這種情況下,打印的下一個值是其他字段,描述字段,和類型。刺符號用于保留調試信息.
?
未知符號類型,或者目標文件特有的符號類型.
命令參數
-t radix
 
--radix=radix
符號值得進制。d 十進制, o 八進制, x 十六進制.
-D
 
--dynamic
顯示動態符號,只在對象是動態時有用.
-f format
 
--format=format
輸出的格式,有"bsd","sysv" 或"posix"可選。默認是“bsd”.
-g
 
--extern-only
只顯示外部符號.
-l
 
--line-numbers
對每一個符號,使用調試信息去查找文件名和行號。
-u
 
--undefined-only
只顯示未定義的符號.
--defined-only
只顯示已定義的符號.
--help
顯示幫助

posted @ 2010-05-06 14:15 不會飛的鳥 閱讀(1238) | 評論 (0)編輯 收藏

僅列出標題
共9頁: 1 2 3 4 5 6 7 8 9 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美激情中文字幕乱码免费| 欧美日韩系列| 久久蜜桃香蕉精品一区二区三区| 久久综合五月| 国内久久视频| 午夜视频一区二区| 日韩视频在线免费| 欧美高清在线一区二区| 在线国产亚洲欧美| 老司机成人在线视频| 欧美一区二区视频在线| 国产精品视频精品视频| 亚洲欧美日韩一区在线| 一区二区欧美视频| 国产精品久久久久久久app| 亚洲午夜久久久久久久久电影网| 亚洲精品系列| 欧美三区美女| 亚洲欧美国产高清va在线播| 亚洲一区欧美| 国产日韩精品视频一区二区三区| 久久se精品一区二区| 性久久久久久久久| 国内精品久久久久久| 免费国产自线拍一欧美视频| 久热精品视频在线免费观看| 亚洲精品美女在线观看播放| 亚洲美女淫视频| 欧美性色视频在线| 久久国产精品久久久久久电车 | 蜜臀av性久久久久蜜臀aⅴ四虎| 一区二区在线免费观看| 欧美激情1区| 欧美日韩三级| 午夜一区在线| 久久精品视频在线看| 亚洲国产精品免费| 一本色道久久综合亚洲精品小说| 99天天综合性| 亚洲欧美精品在线观看| 一区二区在线视频| 99av国产精品欲麻豆| 国产亚洲精品久| 亚洲国产美国国产综合一区二区| 久久中文精品| 亚洲一区二区高清| 久久在线免费| 亚洲天堂成人在线视频| 欧美中文日韩| 一本久道综合久久精品| 久久成人av少妇免费| 亚洲免费激情| 欧美在线三区| 中文亚洲视频在线| 久久久av毛片精品| 亚洲一区二区少妇| 久久亚洲不卡| 性欧美大战久久久久久久免费观看| 久久一区二区视频| 久久激情视频| 欧美日韩视频一区二区| 欧美阿v一级看视频| 国产精品入口| 一本色道久久精品| 亚洲九九九在线观看| 久久久久九九视频| 欧美有码在线视频| 国产精品成人一区二区网站软件 | 欧美一级视频精品观看| 欧美精品一区二区视频| 免费成人网www| 国产亚洲午夜| 亚洲综合视频一区| 亚洲一区二区三区精品视频| 欧美成人在线免费观看| 免费欧美日韩| 好吊一区二区三区| 香蕉免费一区二区三区在线观看| 亚洲一区二区三区中文字幕在线| 欧美国产第一页| 欧美成人蜜桃| 亚洲电影免费观看高清| 久久精品91| 久久精品视频在线看| 国产精品美女久久久久久免费| 亚洲国产精品悠悠久久琪琪| 亚洲电影有码| 麻豆国产精品一区二区三区| 免费久久99精品国产自| 亚洲国产成人tv| 麻豆成人精品| 男人的天堂亚洲| 经典三级久久| 久久精品色图| 欧美v国产在线一区二区三区| 在线看国产一区| 欧美bbbxxxxx| 亚洲精品网站在线播放gif| 一区二区欧美亚洲| 欧美视频免费| 亚洲欧美国内爽妇网| 久久精品国产一区二区三区| 欧美激情精品久久久久久| 国产精品色网| 亚洲欧美日韩一区二区三区在线观看 | 亚洲第一福利视频| 美女网站久久| 亚洲精品日韩在线观看| 亚洲一区欧美| 国产日韩欧美在线播放| 欧美资源在线| 欧美激情网友自拍| 国产精品99久久久久久宅男| 国产精品日韩精品欧美在线| 亚洲欧美一区二区三区极速播放| 久久久久久香蕉网| 亚洲日韩欧美视频| 欧美午夜电影在线| 久久不见久久见免费视频1| 欧美国产成人在线| 亚洲一级黄色| 一区二区三区在线视频免费观看 | 在线中文字幕日韩| 国产精品美女主播| 久久九九久久九九| 日韩亚洲精品视频| 久久亚洲不卡| 亚洲性av在线| 国产亚洲精品久久久久久| 老司机精品福利视频| 亚洲私人影吧| 欧美国产先锋| 欧美在线亚洲在线| 99这里有精品| 好看的亚洲午夜视频在线| 欧美区高清在线| 久久国产精品亚洲va麻豆| 亚洲日本欧美在线| 久久久久综合网| 亚洲五月婷婷| 亚洲激情亚洲| 国产主播精品| 国产精品家庭影院| 欧美成人亚洲成人| 久久精品国产69国产精品亚洲| 日韩视频免费观看高清完整版| 老司机久久99久久精品播放免费| 亚洲欧美日韩精品久久| 亚洲另类在线视频| 在线欧美日韩精品| 国产在线欧美日韩| 国产精品一区二区女厕厕| 欧美精品18| 蜜臀av性久久久久蜜臀aⅴ四虎| 亚洲主播在线播放| 一区二区av| 亚洲经典在线看| 男人的天堂亚洲| 久久嫩草精品久久久精品一| 性伦欧美刺激片在线观看| 亚洲一区二区免费看| 日韩亚洲精品在线| 亚洲精品免费一二三区| 亚洲高清不卡在线| 在线观看欧美视频| 韩日在线一区| 狠狠色丁香久久综合频道| 国产日韩免费| 国产视频精品网| 国产日韩精品视频一区二区三区| 国产精品亚洲视频| 国产女主播在线一区二区| 国产精品免费看久久久香蕉| 国产精品高潮呻吟久久av黑人| 免费亚洲婷婷| 99在线|亚洲一区二区| 欧美不卡激情三级在线观看| 久久久久久高潮国产精品视| 久久精品一本| 久久影视精品| 欧美成人资源网| 亚洲第一黄色网| 91久久香蕉国产日韩欧美9色| 亚洲国产va精品久久久不卡综合| 蜜桃av综合| 欧美国产日本韩| 最新国产乱人伦偷精品免费网站| 最新中文字幕一区二区三区| 亚洲精品乱码久久久久久蜜桃91 | 美女精品国产| 欧美国产在线电影| 最新成人av在线| 国产精品99久久久久久久女警| 亚洲永久免费观看| 欧美在线视频免费播放| 久久在线视频在线| 欧美日韩高清在线一区| 国产精品入口| 亚洲激情国产| 亚洲午夜精品久久久久久浪潮|