存档在 2011年6月

西邮Linux兴趣小组纳新笔试试题

2011年6月16日

下面是西邮Linux小组今年纳新的笔试试题,原文在这里

1、 下面这个程序的输出结果是什么?
int main()
{ int a = (1, 2);
printf(“a = %d\n”, a);
return 0;
}

解答:
a = 2
这里利用了逗号表达式。

2、下面这个程序的输出结果是什么?
struct value {
char a[3];
short b;
};
struct value tmp;
printf(“sizeof(tmp) is %d\n”, sizeof(tmp));

解答:
sizeof(tmp) is 6
这里涉及到字节对齐,a数组后填充了1字节。

3、编写程序交换a,b的值。(使用多种方法)

4、说明 int *p[5] 和 int (*p)[5] 的区别?

解答:前者是一个指针数组,后者是一个数组指针。

5、编写函数,实现链表的创建,节点的插入和删除。

6、下面这个程序的输出结果是什么?
void f(int *p);
int main(void)
{
int *p;
f(p);
printf(“*p = %d\n”, *p);
return 0;
}
void f(int *p)
{
static int dummy = 5;
p = &dummy;
}

解答:
*p中的值不确定。
大多数人(包括我)都会认为*p的值是5,但这并不正确。因为f函数传递的是指针变量,既然是变量,那么只能通过传递地址来确保将f中的值饭或到主函数。如果f传递的是&p,那么最后的结果应该是5。

7、说明如下两段代码的区别。
char * p = “xiyou linux”;
char p[] = “xiyou linux”;

解答:
前者p是一个指针,它指向一段只读字符串。
后者p是一个数组,该数组用“xiyou linux”来初始化。

8、用C语言实现一个n!函数。(要求用递归实现)。

9、 char c;
char b[20] = “I love xiyou linux”;
c=’l’与c=”l”有区别么?若有,区别是什么?字符串b在内存中占几个字节?

解答:
c=’l’是一个字符,而c=”l”是字符串。
从对b数组的定义可以得知b在内存中占有20个字节,这与对它进行初始化的字符串长度无关。
如果有如下定义:
char b[] = “I love xiyou linux”;
那么此时b的大小为19。

10、实现自己的strcat()。

11、char str[20] ;
scanf(“%s”, str);
printf(”%s”,str);
如果输入:I love linux!<回车>
输出结果是什么?为什么?

解答:
输出结果为I。因为使用scanf输入字符串时不能含有空格,否则字符串会从第一个空格处被截断。

12、下面这段程序的输出结果是什么?为什么?
#define MAX(x,y) x > y ? x : y
int main()
{
int a = 10, b = 20;
int c = 3*MAX(a , b);
printf (“%d\n”, c);
return 0;
}

解答:
经过宏替换后,c = 3*a>b?a:b; 则c的值为10。

13、已知两个整型数组均为升序排列,将两个数组合并,且合并后仍按升序排列。

14、下面两条语句哪条更合理?
if(array[which] == 5 && which < SIZE)…..
if(which < SIZE && array[which] == 5)….

TCP连接的建立和终止

2011年6月9日

在基于TCP的客户/服务器模型中,服务器和客户程序之间通过一系列的socket接口形成TCP连接。服务器启动后的首要工作就是创建监听套接字,监听套接字是通过socket、bind和listen三个函数完成的。在这之后,监听套接字通过accept函数一直处于阻塞状态,等待客户程序的连接请求。客户程序在连接至服务器之前,必须先创建自己的套接字,再通过connect函数连接到服务器。如果一切顺利,那么服务器和客户之间就形成了一条TCP连接。

上述TCP连接的过程是从编程角度触发,接下来我们将从网络通信的角度分析TCP的建立和终止过程,并且结合相关的socket函数接口分析其建立和终止过程中客户程序和服务器状态的变化。

1.TCP的建立

TCP连接建立过程即通常我们所说的“三路握手”。整个连接过程是由客户端的connect函数发起的,其具体的步骤如下:

1.客户端向服务器发送一个SYN(同步)报文段,这个报文段包含了客户端将要发送数据的初始序列号,假设该序号为J。该SYN段为整个TCP连接过程的报文段1。

2.服务器向客户端发送一个SYN段作为对客户端的应答,该报文段包含服务器在此连接中将要发送数据的起始序列号K。该SYN段为整个TCP连接过程的报文段2;在该报文段中还包含服务器对客户端SYN段的确认(ACK),该确认包含的序号为J+1,表明服务器期望客户端下次发送的数据序号是从J+1开始的。也就是说报文段2包含服务器发送的SYN和服务器对客户SYN的ACK。

3.客户端向服务器发送ACK以对服务器SYN进行确认,该报文段的序号为K+1。这是报文段3。

由于服务器在接收到客户端的TCP连接请求之前调用了accpet函数,所以一直处于阻塞状态。当客户通过connect函数向服务器发送第一个SYN段时,它执行了主动打开TCP连接这个动作。而接受这个SYN的服务器则执行被动打开动作,它向客户发送SYN以及ACK确认。

接下来我们以上文所举例的回射客户/服务器程序为例,说明TCP连接过程中客户端和服务器的状态变化。

1.从后台启动服务器程序。通过netstat命令我们可以看到当前服务器处于监听状态,因此此刻还没有任何客户请求。

在本地地址一栏中,IP地址为*是因为我们在绑定(bind)服务器套接字时使用了INADDR_ANY参数。由于此刻没有任何客户请求,所以外来地址处均为*。

2。运行客户程序,TCP连接建立。第一条信息为主服务器进程,它此刻仍然处于监听状态,等待其他客户程序的连接请求;第二条信息为子服务器进程,它此刻已经建立了TCP连接,本地端口为6666,而外来连接端口为54260。第三条信息为客户客户进程,他此刻也处于已建立连接的状态,本地端口为54260,而外来端口正好为6666。

此刻,我们在客户端并没有输入任何数据,因此此刻主服务器进程、子服务器进程和客户进程均处于阻塞状态,但是其阻塞原因却不同。主服务器进程是因为等待(accept)其他客户请求而阻塞;子服务器进程是因为等待(read())客户进程发送的数据而阻塞;客户进程则是因为等待从标准输入读取(fgets())数据而阻塞。我们可以进一步使用ps命令查看三个进程之间的状态。如下:

对ps使用-t选项可以查看指定伪终端下的进程,而通过-o选项则可以输入指定的进程状态信息,WCHAN参数可以进程睡眠的原因。ps的输出结果和我们上面的分析的结果一致,三个进程确实处于睡眠状态(S)。当进程阻塞于accept或connect时,其睡眠条件为inet_csk_wait_for_connect;当进程阻塞于套接字输入或输出时,其睡眠条件为sk_wait_data;当进程阻塞于终端的读操作时,其睡眠条件为n_tty_read。

2.TCP的终止

由于TCP连接是全双工的,因此必须分别关闭两个方向上的连接。通常一个TCP连接的终止需要4个分节。整个TCP的终止过程通常是由客户端的close函数发起的。

1.客户端进程调用close函数关闭套接字,它执行了主动关闭TCP连接,该端TCP向服务器发送一个FIN报文段(报文段1),表示该端的数据已经发送完毕。假设该报文段的序号为M。

2.服务器接收到这个FIN段后执行被动关闭。为了确认受到这个FIN,服务器向客户端发送一个ACK(报文段2),该确认报文的序号为M+1。服务器接收了客户端发送来的FIN说明服务器在该TCP连接上再无数据可接收。

3.服务器程序调用close关闭套接字,这将导致服务器向客户端发送一个FIN报文(报文段3),假设该报文的序号为N。

4.客户端为了确认服务器发送来的FIN报文,它向服务器发送一个ACK(报文4),该报文的序号为N+1。

上述所提及的关闭套接字有时候并不是只能通过close函数来实现。客户进程不论是正常终止(通过exit或者从主函数中返回)还是通过某个信号而终止时,都会关闭所有已经打开的描述符,这里当然包括套接字描述符。

当用户在标准输入端输入EOF时(Contrl+D),客户正常退出而关闭套接字,紧接着就会发生上述的TCP终止过程。通过netstat命令可以看到当前TCP的连接状态。

当前连接的客户端进入了TIME_WAIT状态,而服务器仍然在等待其他客户的连接。

我们继续使用ps可以查看到当前客户和服务器进程的状态。

可以看到客户进程已经退出,主服务器进程由于等待其他客户的连接而处于睡眠状态,子服务器进程虽然已经停止了服务,但是仍然处于“阴魂不散”的僵死状态。这是由于主服务器进程并未对子服务器进程发出的SIGCHLD信号进行捕获,具体的捕获方法在这里的文章已经说明。

TCP并发服务器程序设计

2011年6月8日

TCP客户/服务器程序设计模型-并发服务器

上文中所说的迭代服务器简单易懂,将它作为学习socket编程的例子再适合不过了。但是在一般的实际应用中我们并不会使用这个简单的迭代服务器,因为这样的服务器每次只能服务一个客户,如果这个客户请求长时间占用服务器,那么其他客户将一直不能被服务。较为理想的情况是服务器可以同时为多个客户服务,接下来将要说明的并发服务器就可以满足这样的要求。

1.并发服务器原理

关于并发服务器的基本原理可以参考本博客之前的文章,这里不在赘述。下面将通过一个具有并发服务器的C/S模型示例来说明并发服务器程序设计的细节。

2客户程序设计

客户程序与上文所述的客户程序类似,只不过在客户端进行connect之后,客户端与服务器之间的所有数据交互都封装在了str_cli函数中。客户程序主函数在调用该函数时不仅要向该函数传递套接字,还要传递标准输入文件指针stdin。具体代码如下:

void str_cli(FILE *fp, int sockfd)
{
	char sendline[DATA_MAX], recvline[DATA_MAX];

	memset(sendline, 0, DATA_MAX);
	memset(recvline, 0, DATA_MAX);

	while(fgets(sendline, DATA_MAX, fp) != NULL) {
		if (write(sockfd, sendline, strlen(sendline)) < 0) {
			printf("write error!\n");
			exit(1);
		}

		if (read(sockfd, recvline, DATA_MAX) == 0) {
			printf("read error!\n");
			exit(1);
		}
		fputs(recvline, stdout);

          	memset(sendline, 0, DATA_MAX);
         	memset(recvline, 0, DATA_MAX);
	}
}

从客户端的数据交互函数str_cli中可以看出客户端进行的操作如下:

1.客户程序通过fgets从标准输入文件读入数据,并通过write将数据发送至服务器端;

2.客户程序通过read函数读取服务器发来的数据,并通过fputs将数据写入标准输出文件;

3.如果fgets读入数据错误时,整个循环结束,客户端也将结束;

3.服务器程序设计

并发服务器的经典模型在本博客之前的文章中有所提到,这里只是特别说明服务器数据处理函数。

void str_echo(int sockfd)
{
	int n;
	char buf[DATA_MAX];

again:
	while((n = read(sockfd, buf, DATA_MAX)) > 0)
		if (write(sockfd, buf, n) < 0) {
			printf("write error!\n");
			exit(1);
		}

	if (n < 0 && errno == EINTR)
		goto again;
	else if (n < 0)
		printf("str_echo error!\n");
}

服务器程序完成的工作如下:

1.通过read读取客户程序发送的数据;

2.通过write将刚刚读到的数据重新发送给客户程序;

3.如果客户端正常退出,那么服务器的read函数将返回0并退出循环;否则将显示错误信息;

通过上述对客户和服务器程序的描述,我们可以得知该C/S模型的工作方式是:客户程序从标准输入读入用户输入的数据,并发送至服务器;服务器从网络输入读入该数据,立即将读入的数据发送给客户程序;客户从网络输入读取服务器发来的数据,并将其显示在标准输出上。

也就是说,每当用户在客户端输入数据,客户端在终端上又会立即回显用户所输入的数据。因此,我们将上述所举例的具有并发服务器的C/S模型称之为回射客户/服务器程序。

TCP迭代服务器程序设计

2011年6月7日

TCP客户/服务器程序设计模型-迭代服务器

本博客将从本文开始持续更新基于TCP的C/S程序设计基本模型系列文章。一开始我们以一个简单的C/S模型为例进行学习,在后续的文章中我们将给出不同的客户/服务器模型。

下面将要示例的程序具有这样的功能:客户程序与服务器建立一个TCP连接后,服务器以友好的方式将当前的时间和日期发送给客户程序。我们接下来重点对示例程序的设计模式和一些设计细节作以说明,程序中所涉及的socket接口具体可以参考这里的文章。

1.客户程序设计

在基于socket的C/S模型中,客户程序往往比服务器程序简单的多。客户程序通常完成的工作是:通过socket函数创建一个套接字;connect到服务器;如果连接成功,则与服务器进行数据交互,具体为从服务器读取数据或向服务器写入数据。客户程序的核心代码如下:

int main(int argc, char **argv)
{
	int sockfd, n;
	char recvline[MAXLINE + 1];
	struct sockaddr_in serv_addr;

	if (argc != 2) {
		printf("Usage:a.out \n");
		exit(1);
	}

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		my_error("socket", errno, __LINE__);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(4555);

	if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0)
 		my_error("inet_pton", errno, __LINE__);
 	if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1)
  		my_error("connect", errno, __LINE__);
 	while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
		recvline[n] = 0;
		if (fputs(recvline, stdout) == EOF)
			my_error("fputs", errno, __LINE__);
	}

	if (n < 0)
		printf("Error:read error\n");

	exit(0);
}

关于客户端程序的设计,说明如下:

1.客户端程序每次启动时,以命令行参数形式将服务器IP传递给客户端。因此程序一开始必须检测命令行参数的合理性;

2.由于我们的C/S模型是基于TCP的字节流套接字,因此相应的对socket函数使用AF_INET和SOCK_STREAM参数。socket函数返回一个整形的套接字描述符,在客户程序中要使用的connet和read等函数将依靠此描述符来对套接字进行操作;

3.在客户连接至服务器之前前,必须设置网际套接字地址结构,即依次对sockaddr_in结构中的字段填入正确的值。htons函数将主机字节序列转换成网络字节序列;inet_pton函数则是将字符形式的IP地址转换成二进制数值形式;

4.connect将与其第二个参数所指定的服务器建立一个TCP连接。connect函数的第二个参数为通用套接字地址结构sockaddr,因此必须对网际套接字地址结构进行强制类型转换;

5.通过套接字描述符sockfd从服务器端进行读数据,并通过fputs函数将读到的数据显示在标准输出设备;

2.服务器程序设计

服务器程序按照以下步骤进行设计:socket一个套接字;将服务器端口bind到服务器套接字上;通过listen函数将服务器套接字转换成监听套接字;accept客户端的连接;如果连接成功,则与客户端进行数据交互;终止连接。服务器程序核心程序如下所示。

int main(int argc, char **argv)
{
	int listenfd, connfd;
	struct sockaddr_in serv_addr, client_addr;
	char buf[MAXLINE];
	time_t ticks;
	int addr_size;

	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		my_error("socket", errno, __LINE__);

	memset(&serv_addr, 0, sizeof(struct sockaddr_in));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(4555);

	if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1)
		my_error("bind", errno, __LINE__);

	if (listen(listenfd, LISTENQ) == -1)
		my_error("listen", errno, __LINE__);

	for (;;) {
		addr_size = sizeof(struct sockaddr);
		if ((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &addr_size)) == -1)
			my_error("accept", errno, __LINE__);

		printf("%s has connectd to server..\n", inet_ntoa(client_addr.sin_addr));
		ticks = time(NULL);
		snprintf(buf, sizeof(buf), "%.24s\r\n", ctime(&ticks));
		if (write(connfd, buf, strlen(buf)) < 0)
			my_error("write", errno, __LINE__);

		close(connfd);
	}
}

关于服务器程序的相关细节,说明如下:

1.在设置网际套接字地址时,将IP地址指定为INADDR_ANY。如果服务器主机有多个网络接口,那么服务器进程就可以在任意网络接口上接受客户连接;

2.listen函数将套接字转换成监听套接字,这样内核就可以通过该套接字接受客户端的外来连接请求;

3.socket、bind和listen这三步是TCP服务器获得监听套接字描述符的通用步骤;

4.服务器的连续运转是通过死循环来实现的,并通过accept函数使得服务器程序不会过于占据系统内存。通常服务器进程通过accept函数进入阻塞状态,直到某个客户连接到达时才会被唤醒。客户和服务器之间的TCP连接是通过三次握手来建立的,accept函数建立连接成功后会返回另一个套接字描述符——已连接套接字。该套接字用于和刚刚连接的客户进行通信。

5.time函数将返回1970年1月1日至现在的秒数,而ctime函数将这些秒数转换成友好的时间格式;

6.wirte函数通过已连接套接字connfd将时间信息发送至客户端;

通过服务器程序我们可以看到,如果服务器没有处理客户端的请求,那么它将一直处于阻塞状态;如果服务器正在处理来自客户端的连接请求,那么其他请求连接至服务器的客户进程则一直处于阻塞状态。只有当服务器处理完当前客户的连接请求时,其他客户的连接请求才有机会被服务器处理。我们将此服务器称之为迭代式服务器,也就是说服务器对于每个客户都将迭代执行一次。

上述所示例的迭代服务器适用性很差,因为在服务器处理当前的客户请求之前,其余等待的客户程序都不能被处理。但它作为一个基本的C/S模型,对于初学者以及理解后续C/S模型都有极大帮助。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key