APUE笔记:基础socket编程

从下一个星期开始到今年结束,本人会因为研究生入学考试的备考停止做项目9个月,今后的博客将大多数以介绍学习经验为主,本次主要来分享一下基础的socket Linux C程序编写经验。

程序源码

话不多说,先上源码:

socket_client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MESSAGE 	"Hello, this is Donald Shallwing. I'm alread here, please wait for me in UESTC!\n"
#define BUF_SIZE 	256
#define PORT		2018
#define BACK_LO		"127.0.0.1"

int main(int argc, char **argv)
{
	int					client_fd = -1;
	int					connect_fd = -1;
	char 				buffer[BUF_SIZE];
	socklen_t			client_len;	
	struct sockaddr_in	server_addr;
	int					client_read_fd = -1;
	int					client_write_fd = -1;

	client_fd = socket( AF_INET, SOCK_STREAM, 0);
	if(client_fd < 0)
	{
		printf("\nFail to create a new client socket [%d]: %s\n", client_fd, strerror(errno) );
	}
	else
	{
		printf("\nCreate a new client socket [%d]\n", client_fd);
	}
	
	memset( &server_addr, 0, sizeof(server_addr) );
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	inet_aton( BACK_LO, &server_addr.sin_addr );
	
	connect_fd = connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr) );
	if(connect_fd < 0)
	{
		printf("Fail to connect with the server computer [%d]: %s\n", connect_fd, strerror(errno));
		return connect_fd;  
	}
	else
	{
		printf("Complete to connect with the server computer\nIP:%s\n", inet_ntoa(server_addr.sin_addr) ); 
	}

	
	//read & write
	client_read_fd = read( client_fd, buffer, sizeof(buffer) );
	if(client_read_fd < 0)
	{
		printf("Fail to read the buffer of the server computer [%d]: %s\n", client_read_fd, strerror(errno));
 	}
	else
	{
		printf("The content of the buffer in server computer is:\n%s\n", buffer);
	}
	
	client_write_fd = write( client_fd, MESSAGE, sizeof(MESSAGE) );
   	if(client_write_fd < 0)
    {
        printf("Fail to write MESSAGE [%d]: %s\n", client_write_fd, strerror(errno) );
        return client_write_fd;
    }
    else
    {
        printf("Complete to write MESSAGE in client_fd!\n\n");
    }
    
	close(client_fd);
	close(client_write_fd);
	close(client_read_fd);
	close(connect_fd);

	return 0;
}

socket_server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LISTEN_PORT	2018
#define BUF_SIZE 	256 
#define BACKLOG		8
#define MESSAGE		"Hello,my name is socket,I originate from Ubuntu OS!\n"

/*struct sockaddr_in{
	sa_family_t	sin_family;
	unit16_t	sin_port;
	struct in_addr	sin_addr;
	char		sin_zero[8];
}*/

int main(int argc, char **argv)
{
	socklen_t						               client_addrlen;
	int							                   bind_fd = -1;
	int								               listen_fd = -1;
	int								               client_fd = -1;
	int								               client_read_fd = -1;
	int								               client_write_fd = -1;
	int								               reuse = 1;
	char								               buffer[BUF_SIZE];
	struct	sockaddr_in		               server_addr;
	struct	sockaddr_in		               client_addr;
	
	
	listen_fd = socket(AF_INET, SOCK_STREAM , 0);
	if(listen_fd < 0)
	{
		printf("\nCan't create a listener fd [%d]: %s\n", listen_fd, 
		strerror(errno));
		return listen_fd;
	}
	printf("\nA socket has been created: [%d]\n", listen_fd);
	

	//Close the port after halting the progress or run it again
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, reuse);


	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons( LISTEN_PORT );
	server_addr.sin_addr.s_addr = htonl( INADDR_ANY );

	bind_fd = bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); 
	if(bind_fd < 0)
	{
		printf("Fail to bind the port [%d]: %s\n", bind_fd, 
		strerror(errno));
		return bind_fd;
	}
	else
	{
		printf("Socket[%d] bind successfully!\n\n", listen_fd );	
	}


	listen(listen_fd, BACKLOG);
	while(1)
	{
		//Accept a client
		printf("Start connecting...\n\n");
		client_fd = accept( listen_fd, (struct sockaddr*)&client_addr, &client_addrlen);
		if( client_fd < 0 )
		{
			printf("Fail to accept client host [%d]: %s\n", client_fd, 
			strerror(errno));
			break;
		}
		else
		{
			printf("A new client host has connected\nIP:%s port:%d \n",
			inet_ntoa( client_addr.sin_addr ), ntohs(client_addr.sin_port) );
		}
		
		//Server read & write
		memset(buffer, 0, sizeof(buffer));
		client_write_fd = write(client_fd, MESSAGE, sizeof(MESSAGE) );
		if( client_write_fd < 0)
		{
			printf("Fail to write the MESSAGE [%d]: %s\n", client_write_fd, strerror(errno) );
			continue;
		}
		else
		{
			printf("Complete to send MESSAGE!\n");
		}	

		memset(buffer, 0, sizeof(buffer));
		client_read_fd = read( client_fd, buffer, sizeof(buffer) );
		if( client_read_fd < 0 )
		{
			printf("Fail to read in the client host[%d]: %s", client_read_fd, 
			strerror(errno) );
		}
		else 
		{
			if( client_read_fd == 0 )
			{
				printf("The client host[%d][%d] is disconnected!\n", client_fd, 
				ntohl(client_addr.sin_addr.s_addr) );
			}
			else
			{
				printf("The content of the host is:\n%s\n\n", buffer);
			}
		}
	}

	close( listen_fd );
	close( client_fd );
	close( client_read_fd );
	close( bind_fd );

	return 0;
}

以上是本人写的第一个socket程序。read和write不是socket编程中特有的函数,相应的,socket编程里还有与之类似的recv和send函数,也是用来对文件描述符进行读写的,用法比read和write简单。后来的编程中,本人逐渐用recv和send函数代替了read和write。

基础的socket编程只用达到一个目标,就是实现客户端和服务器之间简单的通信,实现此目标的核心便是建立服务器和客户端之间的socket连接,而建立连接的基础便是理清服务器端和客户端建立的流程。
服务器端的建立:

  1. 先调用socket函数, 成功调用之后,该函数会返回一个服务器端的文件描述符,用来进行之后的服务器端的搭建和属性设置;
  2. 调用bind函数,绑定服务器端的IP和端口。当定义了一个sockaddr_in 结构体类型的变量,并给其中的成员sin_family、sin_port、sin_addr变量赋完值,再把此结构体作为参数传给了bind函数后,此步骤才算完成;
  3. 调用listen函数,监听你之前设置的socket通信端口;
  4. listen函数调用之后,服务器端的就已经搭建完毕,当有客户端来连接时,此时应该调用accept函数来连接客户端,此函数返回一个用来与客户端通信的文件描述符,与之前的socket函数返回的文件描述符不同,此文件描述符不用来进行服务器的设置,而用于与客户端的通信。

客户端的建立:

  1. 同服务器端一样,客户端首先就应该调用socket函数创建一个用于socket连接的文件描述符;
  2. socket函数调用成功之后,此时就可以直接与服务器端建立连接了,调用connect函数即可实现;
  3. connect函数也会返回一个文件描述符,用于之后的客户端与服务器端的通信进程。

经常写socket程序的人就很清楚,socket通信总结起来,客户端与服务器端在程序里面无外乎各有两个动作:服务器端先应该搭建好服务器的框架,然后再去接收客户端文件描述符并与之通信客户端先搭建好socket并与连接上服务器,然后再去读写服务器端发来的数据。这些是socket通信程序的统一流程(本人上一篇有关FTP文件上传与下载的博客中,客户端的行为不也是先搭建好socket然后利用FTP命令来与FTP服务器之间进行数据传输的吗)

本人经验的分享

初学者接触Unix网络socket编程,会表现出无从下手,思维混乱的情况,这很正常,本人在此讲述一下初次学习socket编程的方法和今后写socket程序的习惯。

  • 初次学习socket编程时,理清楚样例代码的程序流程最重要,一个简单的socket程序就有超过100行的代码,函数繁而杂,此时死记硬背代码是不管用的,只有将程序的流程理清,才可以对程序中每一个函数的调用和整个socket编程的核心得以掌握;
  • 初次接触网络编程里面的函数,函数太多也不要紧,一个一个函数地理解与消化,理清楚程序的流程之后,此时你就可以按照流程一个函数一个函数地学习,如果man手册上函数的解析你看不懂,就上网搜索该函数的相关用法,必要时在NotePad++上记录函数的形式以及使用方法等信息,这比你通篇代码死记硬背要好许多;
  • 今后牢记socket编程相关的函数的参数列表以及参数类型,是没必要的。熟悉了socket编程的通用流程之后,该什么时候调用哪个函数,你也就相应的清楚了。本人在今后写socket程序时,一定会忘记某个函数的相关用法,本人对此通常的做法就是在Linux系统上man一下,之后该函数的参数列表以及调用规则一清二楚,根本没必要故意地去记忆某一个函数的所有参数等信息。当然,在某些大型企业的笔试过程中,是不允许打开Linux man手册的,这时候就需要读者短暂地记忆某些重要函数的详细用法与参数列表了。
  • 程序写好后编译执行,发现程序长时间连接不成功或者抛出段错误的异常,此时应该首先检查网络状态是否正常,因为网络正常是保证socket程序正常执行的必要因素。排除了网络故障之后,可以尝试用gdb调试,不会使用gdb调试器的读者,可以尝试用调用printf的方式找到错误原因,一般来说,段错误的产生无外乎两种:修改了只读数据段的内容、操作了空指针,后者体现在socket程序中接收到的文件描述符内容为空,在printf对应buf的过程中产生了段错误。不仅仅是socket编程,所有的APUE之中,都应该考虑函数调用失败的情况,即有容灾机制,典型的容灾机制就是上面代码里的strerror(errno)。

以上便是本人学习socket编程中的一些细节与方法,希望读者阅读完此篇博客之后有所收获,编程更上一层楼!

你可能感兴趣的:(APUE)