C语言之网络编程(服务器和客户端)

Linux网络编程

1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。

常用的TCP/IP协议的3种套接字类型如下所示。

(1)流套接字(SOCK_STREAM):

流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission ControlProtocol)协议

(2)       数据报套接字(SOCK_DGRAM):

数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

(3)       原始套接字(SOCK_RAW):(一般不用这个套接字)

原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW

2、 套接字基本函数:

(1)      创建套接字:int socket(int family, int type, intprotocol);

功能介绍:

      在Linux操作系统中,一切皆文件,这个大家都知道,个人理解创建socket的过程其实就是一个获得文件描述符的过程,当然这个过程会是比较复杂的。可以从内核中找到创建socket的代码,并且socket的创建和其他的listen,bind等操作分离开来。socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。

参数说明:

      从socket创建的函数可以看出,socket有三个参数,family代表一个协议族,比较熟知的就是AF_INET,PF_PACKET等;第二个参数是协议类型,常见类型是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三个参数是具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。

(2)      套接字绑定函数: intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

功能介绍:

      bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度 structsockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。在客户端模式中不需要使用bind函数。当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。

参数说明:

      bind函数的第一个参数sockfd是在创建socket套接字时返回的文件描述符。

      bind函数的第二个参数是structsockaddr类型的数据结构,由于structsockaddr数据结构类型不方便设置,所以通常会通过对tructsockaddr_in进行地质结构设置,然后进行强制类型转换成structsockaddr类型的数据,

 

(3)      监听函数:int listen(int sockfd, int backlog);

功能介绍:

      刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度有listen中的backlog参数来设定。listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。当listen运行成功时,返回0;运行失败时,返回值位-1.

参数说明:

      sockfd是前面socket创建的文件描述符;backlog是指server端可以缓存连接的最大个数,也就是等待队列的长度。

 

(4)      请求接收函数: int accept(int sockfd, structsockaddr *client_addr, socklen_t *len);

功能介绍:

      接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。

参数说明:

      sockfd是socket创建的文件描述符;client_addr是本地服务器端的一个structsockaddr类型的变量,用于存放新连接的协议族,网络地址以及端口号等;第三个参数len是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(structsockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。

(5)客户端请求连接函数: intconnect(int sock_fd, struct sockaddr *serv_addr,int addrlen);

功能介绍:

      连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。

参数说明:

      connect的第一个参数是socket创建的文件描述符;第二个参数是一个structsockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号;第三个参数表示第二个参数内容的大小,与accept不同,这个值不是一个指针。

      

      在服务器端和客户端建立连接之后是进行数据间的发送和接收,主要使用的接收函数是recv和read,发送函数是send和write。因为对于socket套接字来说,最终实际操作的是文件描述符,所以可以使用对文件进行操作的接收和发送函数对socket套接字进行操作。read和write函数是文件编程里的知识,所以这里不再做多与的赘述。

 

3、 有了以上的知识,那么我们就可以编写一个简单的服务器和客户端了

(1)       简易服务器:这个服务器只能与一个客户端相连接,如果有多个客户端就不能用这个服务器进行连接。

代码:

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


#define PORT 9990   //端口号
#define SIZE 1024   //定义的数组大小

int Creat_socket()    //创建套接字和初始化以及监听函数
{
	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);   //创建一个负责监听的套接字
	if(listen_socket == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	
	addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口号 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
	
	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));  //连接
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}
	
	ret = listen(listen_socket, 5);        //监听
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}
	return listen_socket;
}

int wait_client(int listen_socket)
{
	struct sockaddr_in cliaddr;
	int addrlen = sizeof(cliaddr);
	printf("等待客户端连接。。。。\n");
	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);   //创建一个和客户端交流的套接字
	if(client_socket == -1)
	{
		perror("accept");
		return -1;
	}
	
	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
	
	return client_socket;
}

void hanld_client(int listen_socket, int client_socket)   //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
	char buf[SIZE];
	while(1)
	{
		int ret = read(client_socket, buf, SIZE-1);
		if(ret == -1)
		{
			perror("read");
			break;
		}
		if(ret == 0)
		{
			break;
		}
		buf[ret] = '\0';
		int i;
		for(i = 0; i < ret; i++)
		{
			buf[i] = buf[i] + 'A' - 'a';
		}
		
		printf("%s\n", buf);
		write(client_socket, buf, ret);
		
		if(strncmp(buf, "end", 3) == 0)
		{
			break;
		}
	}
	close(client_socket);
}

int main()
{
	int listen_socket = Creat_socket();
	
	int client_socket = wait_client(listen_socket);
	
	hanld_client(listen_socket, client_socket);
	
	close(listen_socket);
	
	return 0;
}


(2) 多进程并发服务器:该服务器就完全弥补了上一个服务器的不足,可以同时处理多个客户端,只要有客户端来连接它,他就能响应。在我们这个服务器中,父进程主要负责监听,所以在父进程一开始就要把父进程的接收函数关闭掉,防止父进程在接收函数处阻塞,导致子进程不能创建成功。同理,子进程主要负责接收客户端,并做相关处理,所以子进程在一创建就要把监听函数关闭,不然会导致服务器功能的紊乱。这个服务器有一个特别要注意的是,子进程在退出时会产生僵尸进程,所以我们一定要对子进程退出后进行处理。

代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>

#define PORT 9990
#define SIZE 1024

int Creat_socket()         //创建套接字和初始化以及监听函数
{
	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //创建一个负责监听的套接字  
	if(listen_socket == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	
	addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口号 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
	
	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //连接
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}
	
	ret = listen(listen_socket, 5);   //监听
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}
	return listen_socket;
}

int wait_client(int listen_socket)
{
	struct sockaddr_in cliaddr;
	int addrlen = sizeof(cliaddr);
	printf("等待客户端连接。。。。\n");
	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //创建一个和客户端交流的套接字
	if(client_socket == -1)
	{
		perror("accept");
		return -1;
	}
	
	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
	
	return client_socket;
}

void hanld_client(int listen_socket, int client_socket)    //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
	char buf[SIZE];
	while(1)
	{
		int ret = read(client_socket, buf, SIZE-1);
		if(ret == -1)
		{
			perror("read");
			break;
		}
		if(ret == 0)
		{
			break;
		}
		buf[ret] = '\0';
		int i;
		for(i = 0; i < ret; i++)
		{
			buf[i] = buf[i] + 'A' - 'a';
		}
		
		printf("%s\n", buf);
		write(client_socket, buf, ret);
		
		if(strncmp(buf, "end", 3) == 0)
		{
			break;
		}
	}
	close(client_socket);
}

void handler(int sig)
{
	
	while (waitpid(-1,  NULL,   WNOHANG) > 0)
	{
		printf ("成功处理一个子进程的退出\n");
	}
}

int main()
{
	int listen_socket = Creat_socket();
	

	signal(SIGCHLD,  handler);    //处理子进程,防止僵尸进程的产生
	while(1)
	{
		int client_socket = wait_client(listen_socket);   //多进程服务器,可以创建子进程来处理,父进程负责监听。
		int pid = fork();
		if(pid == -1)
		{
			perror("fork");
			break;
		}
		if(pid > 0)
		{
			close(client_socket);
			continue;
		}
		if(pid == 0)
		{
			close(listen_socket);
			hanld_client(listen_socket, client_socket);
			break;
		}
	}
	
	close(listen_socket);
	
	return 0;
}


(3) 多线程并发服务器:上一个多进程服务器有一个缺点,就是每当一个子进程得到响应的时候,都要复制父进程的一切信息,这样就导致了CPU资源的浪费,当客户端有很多来连接这个服务器的时候,就会产生很多的子进程,会导致服务器的响应变得很慢。所以我们就想到了多线程并发服务器,我们知道线程的速度是进程的30倍左右,所以我们就用线程来做服务器。

代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>


#define PORT 9990
#define SIZE 1024

int Creat_socket()         //创建套接字和初始化以及监听函数
{
	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //创建一个负责监听的套接字  
	if(listen_socket == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	
	addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口号 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
	
	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //连接
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}
	
	ret = listen(listen_socket, 5);   //监听
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}
	return listen_socket;
}

int wait_client(int listen_socket)
{
	struct sockaddr_in cliaddr;
	int addrlen = sizeof(cliaddr);
	printf("等待客户端连接。。。。\n");
	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //创建一个和客户端交流的套接字
	if(client_socket == -1)
	{
		perror("accept");
		return -1;
	}
	
	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
	
	return client_socket;
}

void hanld_client(int listen_socket, int client_socket)    //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
	char buf[SIZE];
	while(1)
	{
		int ret = read(client_socket, buf, SIZE-1);
		if(ret == -1)
		{
			perror("read");
			break;
		}
		if(ret == 0)
		{
			break;
		}
		buf[ret] = '\0';
		int i;
		for(i = 0; i < ret; i++)
		{
			buf[i] = buf[i] + 'A' - 'a';
		}
		
		printf("%s\n", buf);
		write(client_socket, buf, ret);
		
		if(strncmp(buf, "end", 3) == 0)
		{
			break;
		}
	}
	close(client_socket);
}

int main()
{
	int listen_socket = Creat_socket();
	
	while(1)
	{
		int client_socket = wait_client(listen_socket);
		
		pthread_t id;
		pthread_create(&id, NULL, hanld_client, (void *)client_socket);  //创建一个线程,来处理客户端。
		
		 pthread_detach(id);   //把线程分离出去。
	}
	
	close(listen_socket);
	
	return 0;
}

(4)客户端:客户端相对于服务器来说就简单多了,客户端只需要创建和服务器相连接的套接字,然后对其初始化,然后再进行连接就可以了,连接上服务器就可以发送你想发送的数据了。
代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


#define PORT 9990
#define SIZE 1024

int main()
{
	int client_socket = socket(AF_INET, SOCK_STREAM, 0);   //创建和服务器连接套接字
	if(client_socket == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	
	addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口号 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
	inet_aton("127.0.0.1", &(addr.sin_addr));

	int addrlen = sizeof(addr);
	int listen_socket =  connect(client_socket,  (struct sockaddr *)&addr, addrlen);  //连接服务器
	if(listen_socket == -1)
	{
		perror("connect");
		return -1;
	}
	
	printf("成功连接到一个服务器\n");
	
	char buf[SIZE] = {0};
	
	while(1)        //向服务器发送数据,并接收服务器转换后的大写字母
	{
		printf("请输入你相输入的:");
		scanf("%s", buf);
		write(client_socket, buf, strlen(buf));
		
		int ret = read(client_socket, buf, strlen(buf));
		
		printf("buf = %s", buf);
		printf("\n");
		if(strncmp(buf, "END", 3) == 0)     //当输入END时客户端退出
		{
			break;
		}
	}
	close(listen_socket);
	
	return 0;
}






  • 83
    点赞
  • 526
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
### 回答1: 使用C语言套接字发送HTTP协议需要遵循以下步骤: 1. 创建套接字: 使用socket函数创建一个套接字。可以选择使用TCP套接字或UDP套接字。通常,使用TCP套接字与HTTP协议交互。 2. 建立连接: 使用connect函数将套接字连接到服务器。在使用HTTP时,通常连接到服务器的80端口。 3. 构建HTTP请求: 构建一个符合HTTP协议规范的请求。请求由请求行,请求头和请求正文三部分组成。在请求行中指定请求方法,请求URL和HTTP版本。请求头中包含其他的请求信息。 4. 发送HTTP请求: 使用send函数将构建好的HTTP请求发送给服务器。 5. 接收HTTP响应: 使用recv函数接收服务器返回的HTTP响应。HTTP响应由响应行,响应头和响应正文三部分组成。在响应行中指定HTTP版本,状态码和状态短语。响应头中包含其他的响应信息。 6. 处理HTTP响应: 根据HTTP响应中的状态码和响应内容进行处理。通常,状态码为200表示请求成功。响应正文中包含服务器返回的信息。 7. 关闭套接字: 使用close函数关闭套接字。 通过以上步骤,就可以使用C语言套接字发送HTTP协议了。 ### 回答2: 要使用C语言套接字发送HTTP协议,可以按照以下步骤进行操作: 1. 导入必要的C语言库: 在程序中导入相应的C语言库,如`stdio.h`、`stdlib.h`、`string.h`和`sys/socket.h`等。 2. 创建套接字: 使用`socket`函数创建套接字,指定协议为`AF_INET`,类型为`SOCK_STREAM`,并将返回的文件描述符保存起来。 3. 设置目标服务器地址: 通过`struct sockaddr_in`结构来设置目标服务器的IP地址和端口号,并使用`inet_pton`函数将IP地址转换为网络字节顺序。 4. 连接到目标服务器: 使用`connect`函数连接到目标服务器,传入套接字文件描述符和目标服务器地址。 5. 构建HTTP请求消息: 构建HTTP请求消息,并使用`write`函数将请求消息发送给目标服务器。 6. 接收HTTP响应消息: 使用`read`函数从服务器接收HTTP响应消息,并将其保存在缓冲区中。 7. 处理接收到的响应消息: 根据HTTP响应消息的格式和内容进行相应的处理,例如提取响应头信息、提取响应体信息等。 8. 关闭套接字: 使用`close`函数关闭套接字,释放资源。 需要注意的是,以上步骤只是一个简单的示例,实际开发中可能需要处理更多的细节和错误处理。同时,还可以考虑使用现有的HTTP库来简化开发过程,如libcurl等。 ### 回答3: 要使用C语言套接字发送HTTP协议,你需要遵循以下步骤: 1. 创建一个套接字:使用`socket`函数创建一个套接字。该函数接受三个参数:地址族(通常为`AF_INET`),套接字类型(通常为`SOCK_STREAM`)和协议(通常为`IPPROTO_TCP`)。 2. 连接到服务器:使用`connect`函数将套接字连接到服务器的地址和端口。需要提供服务器的IP地址和端口号。 3. 构建一个HTTP请求:使用C语言字符串操作函数,创建一个满足HTTP协议要求的请求头。请求头中包含HTTP方法(如GET、POST)、请求的URL路径、HTTP协议版本和其他相关头部字段(如Host等)的信息。 4. 将请求发送到服务器:使用`send`函数将构建好的HTTP请求发送到服务器。该函数接受套接字句柄、发送缓冲区地址和要发送的字节数作为参数。 5. 接收服务器的响应:使用`recv`函数接收服务器返回的响应数据。该函数接受套接字句柄、接收缓冲区地址和要接收的字节数作为参数。 6. 处理服务器的响应:分析服务器的响应,提取所需的信息,如HTTP状态码、响应头和响应体等。 7. 关闭连接:使用`close`函数关闭套接字连接。 以下是一个示例代码,演示如何使用C语言套接字发送HTTP请求: ```c #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> int main() { int sockfd; struct sockaddr_in server_addr; char request[] = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"; char response[8192]; // 创建套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(80); inet_pton(AF_INET, "127.0.0.1", &(server_addr.sin_addr.s_addr)); // 连接服务器 connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 发送HTTP请求 send(sockfd, request, strlen(request), 0); // 接收服务器响应 recv(sockfd, response, sizeof(response), 0); // 处理服务器响应 printf("Response:\n%s\n", response); // 关闭连接 close(sockfd); return 0; } ``` 以上代码是一个简单的示例,仅发送了一个GET请求到指定的服务器,并打印了服务器返回的响应。根据实际需求,你可能需要根据HTTP协议规范来构建更复杂的请求和处理响应的逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值