socket编程

linux服务端代码如下:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<signal.h>
#include<fcntl.h>
//手动设置连接台数
#define CONNMAX 1000

char *WorkPath;
int listenfd, clients[CONNMAX];
void error(char *x);
void startServer(char *x);
void respond(int);

int main(int argc, char* argv[])
{
	struct sockaddr_in clientaddr;
	socklen_t addrlen;
	char c, PORT[6];
	//设置当前路径
	WorkPath = getenv("PWD");
	//默认端口
	strcpy(PORT,"10000");
	// 当前socket标识符下标
	int slot=0;
	
	//从命令行接收参数
	while ((c = getopt (argc, argv, "p:r:")) != -1)
		switch (c)
		{
			case 'r':
				WorkPath = malloc(strlen(optarg));
				strcpy(WorkPath,optarg);
				break;
			case 'p':
				strcpy(PORT,optarg);
				break;
			case '?':
				fprintf(stderr,"参数错误\n");
				exit(1);
			default:
				exit(1);
		}

	printf("服务器开放端口 %s 使用 %s 作为当前路径\n",PORT,WorkPath);
	// 设置所有的套接字标识符的默认值为-1,代表这个套接字标识号没有没有使用
	int i;
	for (i=0; i<CONNMAX; i++)
		clients[i]=-1;
	//先开启服务,执行流程中的1,2,3
	startServer(PORT);

	// 持续接收请求
	while (1)
	{
		//包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值
		addrlen = sizeof(clientaddr);
		//4、接收来自客户端的连接请求 accept
		//accept函数等待来自客户端的连接请求到达"监听描述符listenfd"----客户端会调用connect函数发送客户端的"套接字地址"【ip:port】
		//accpet将客户端的套接字地址填写到clientaddr这个结构中,并返回“已连接描述符connfd”
		clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);
		//出错返回-1
		if (clients[slot]<0)
			error ("accept()函数出错,接收失败");
		else{
			//accept返回“已连接描述符后”,创建子进程,让子程序完成response。父进程继续监听
			if ( fork()==0 ){
				//子进程应该关掉监听描述符【因为继续父进程全部变量】,只保留已连接描述符
				close(listenfd);
				//对连接作出响应
				respond(slot);
				exit(0);
			}
		}
		
		//-1表示当前位置无描述符,这步用来寻找数组中下一个可用的已连接描述符的位置
		while (clients[slot]!=-1) 
			slot = (slot+1)%CONNMAX;
		//父进程应该关掉已连接描述符
		close(clients[slot]);
	}

	return 0;
}

//服务端进程做的“监听准备”
void startServer(char *port)
{
	//hits结构可用于提供要产生的套接字类型的信息
	struct addrinfo hints, *res, *p;
	// 将hints结构清空
	memset (&hints, 0, sizeof(hints));
	//使用的协议簇AF_INET=ipv4/AF_INET6=ipv6
	hints.ai_family = AF_INET;
	//协议类型,设置为流类型
	hints.ai_socktype = SOCK_STREAM;
	//设置标志为AI_PASSIVE,告诉函数,要产生的套接字,会被服务端当作“监听套接字”。否则默认返回"主动套接字"
	hints.ai_flags = AI_PASSIVE;

	//将NULL表示得到的套接字地址结构中,ip字段是通配符地址。告诉内核让这个服务器接受发送到此主机的全部请求
	//返回结果是个链表,链表包含的是很多套接字地址,存入res中
	if (getaddrinfo( NULL, port, &hints, &res) != 0)
	{
		perror ("getaddrinfo() 出错");
		exit(1);
	}

	// 1、创建 套接字描述符
	// 2、将“套接字描述符”与“套接字地址”绑定
	for (p = res; p!=NULL; p=p->ai_next)
	{
		//若成功则返回 套接字描述符,若出错则返回-1
		listenfd = socket (p->ai_family, p->ai_socktype, 0);
		if (listenfd == -1) 
			continue;
		//将“套接字描述符”与“套接字地址【ip:port】”绑定
		//调用bind的函数之后,通过套接字描述符来读取或发送到套接字地址
		if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) 
			break;
		//绑定失败,关掉当前描述符并尝试下一个
		close(listenfd);
	}
	if (p==NULL)
	{
		perror ("socket() 或 bind()");
		exit(1);
	}
	//释放这个链表
	freeaddrinfo(res);

	// 3、转换成监听套接字
	// 客户端发起的请求是主动实体,默认情况下,内核会认为socket函数创建的描述符是“主动套接字”,默认存在于客户端。但服务端,需要使用listen函数,
	//来告诉内核,这个描述符是给服务端用的。
	//listen函数将 这个描述符 从主动套接字,变成“监听套接字”。“监听套接字”可以接受客户端的连接请求。
	if ( listen (listenfd, 1000000) != 0 )
	{
		perror("listen()函数转换出错");
		exit(1);
	}
}

//服务端的响应
void respond(int n)
{
	//mesg缓冲区用来存放recv函数接收到的数据
	char mesg[99999], *reqline[3], data_to_send[1024], path[99999];
	//rcvd用来接收copy的字节数
	int rcMegLen, file, bytes_read;
	memset( (void*)mesg, (int)'\0', 99999 );
	//5、从socket中读取字符
	//第一个参数指定接收端套接字描述符
	//第二个参数缓冲区用来存放recv函数接收到的数据;
	//第三个参数指明缓冲区的长度;
	//第四个参数一般置0。
	rcMegLen=recv(clients[n], mesg, 99999, 0);
	//如果recv在copy时出错,那么它返回SOCKET_ERROR
	if (rcMegLen<0) 
		fprintf(stderr,("无法接收消息\n"));
	//如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
	else if (rcMegLen==0)    
		fprintf(stderr,"与客户端的连接意外中断.\n");
	else    // 接收到消息
	{
		//打印客户端请求信息
		printf("%s\n", mesg);
		//按换行进行切分消息
		reqline[0] = strtok (mesg, " \t\n");
		if ( strncmp(reqline[0], "GET\0", 4)==0 )
		{
			reqline[1] = strtok (NULL, " \t");
			reqline[2] = strtok (NULL, " \t\n");
			if (strncmp( reqline[2], "HTTP/1.0", 8)!=0 && strncmp( reqline[2], "HTTP/1.1", 8)!=0 )
			{
				write(clients[n], "HTTP/1.1 400 Bad Request\n", 25);
			}
			else
			{
				//如果url后面不写任何路径,默认为index.html
				if ( strncmp(reqline[1], "/\0", 2)==0 )
					reqline[1] = "/index.html";        
				strcpy(path, WorkPath);
				strcpy(&path[strlen(WorkPath)], reqline[1]);
				printf("向套接字描述符%d号发送了文件: %s\n",clients[n], path);
				printf("========================================================\n");
				//如果文件存在,向客户端发送这个文件,以只读方式打开文件
				if ( (file=open(path, O_RDONLY))!=-1 )   
				{
					// 5.5、向socket写入信息
					//第一个参数指定发送端套接字描述符
    				//第二个参数指明一个存放应用程序要发送数据的缓冲区
    				//第三个参数指明实际要发送的数据的字节数
    				//第四个参数一般置0
					send(clients[n], "HTTP/1.1 200 OK\n\n", 17, 0);
					//向套接字发送数据
					while ( (bytes_read=read(file, data_to_send, 1024))>0 )
						write (clients[n], data_to_send, bytes_read);
				}
				// 否则提示此文件不存在
				else    write(clients[n], "HTTP/1.1 404 Not Found\n", 23); 
			}
		}
	}
	//来禁止套接字上的输入/输出,使用SHUT_RDWR则将同时无法读取和发送数据
	shutdown (clients[n], SHUT_RDWR);       
	//等到客商退出后才会close  
	//6、关闭socket
	close(clients[n]);
	//可以重新使用
	clients[n]=-1;
}

默认返回的index.html

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>恭喜</title>
    <style>
        body{
            background-color: antiquewhite;
            background: url(./bgimage.jpg)no-repeat;
            background-size: 100%;
        }
        .container {
            width: 60%;
            margin: 10% auto 0;
            background-color: #f0f0f0;
            padding: 2% 5%;
            border-radius: 10px;
            text-align:center;
        }
        ul {
            padding-left: 20px;
        }

        ul li {
            line-height: 2.3
        }

        a {
            color: #20a53a
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>访问成功</h1>
        <h3>这是默认index.html</h3>
        <ul>
        </ul>
    </div>
</body>
</html>