socket编程:简单TCP服务器/客户端编程

其实对于socket:我们需要理解的是他提供了一种编程概念,利用socket就可以利用系统已经封装好的内部进行通信,我们只需要关注应用层方面的数据控制就OK了。

一. 套接字(socket)

socket英文为插座的意思,也就是为用户提供了一个接入某个链路的接口。而在计算机网络中,一个IP地址标识唯一一台主机,而一个端口号标识着主机中唯一一个应用进程,因此“IP+端口号”就可以称之为socket

两个主机的进程之间要通信,就可以各自建立一个socket,其实可以看做各自提供出来一个“插座”,然后通过连接上“插座”的两头也就是由这两个socket组成的socket pair就标识唯一一个连接,以此来表示网络连接中一对一的关系。

我们先来简单的了解一下socket究竟是什么东东。

首先对于套接字编程,他需要指定的套接字的地址作为参数,所以在网络协议中存在不同的协议,即存在不同的地址结构定义方式。对于这些结构都是sockaddr_开头的。每一个协议有一个唯一的后缀,例如以太网的就是sockaddr_in,这个常用。

然后我们来看一下通用的套接字结构:

struct sockaddr{
    sa_family sa_family;
    char sa_data[14];
}

这个是套接字的原型,注意在套接字编程中,关于sockaddr_的函数都需要进行类型转换转换为sockaddr.

以太网中常用的套接字结构是:

对应关系是:

sin_family:对应的是地址类型:AF_INET代表ipv4。

sin_port:代表端口号。

sin_addr.s_addr:代表我们所建立的ip地址。

在编程之前,我们需要关注的是,在计算机中,字节序的存储分为大端小段,在网络字节序中,利用的是大端状态。而计算机可能存在大端可能存在小端,所以就存在一些字节序的转换函数:

就如函数名一样。host字节序to转换为net字节序l long4字节长度,余下函数同理。

然后我们还需要关注的是,在我们的sockaddr_in中,ip地址存在sin_addr.s_addr的类型,字符串的点分十进制的类型,还有二进制的类型,所以就有一系列的ip地址结构转换函数:

根据这几个函数的输入参数和输出参数可以看到他们的转换时从什么转换到什么,

然后还有2个安全转换的函数:

这2个函数是针对不同协议族的地址转换,第一个参数就代表网络类型协议族。

在基于上面的了解情况下,我们了解一下socket基于Tcp协议实现可靠传输的连接传输释放过程:

然后我们需要注意一下几个问题:

  1. 客户端与服务器的交互过程:

客户端的连接过程,对服务端是接收过程。然后在过程中进行3次握手建立TCP连接。

客户端与服务端之间的数据交换是相对的过程,客户端的读数据对应的是服务器端的写数据过程。客户端的写数据对应服务器的读数据过程。

在交互完毕后,关闭套接字连接。

下面开始套接字编程的认识。

首先我们来看一下代码,然后进行讲解:

首先来看服务端的代码:

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

#define _PROT_ 8888
#define _BACKLOG_ 5

void process_conn_server(int s)
{
	ssize_t size =  0;
	char buffer[1024];
	while(1)
	{
		size = read(s,buffer,1024);

		if(size == 0)
		{
			return ;
		}

		sprintf(buffer,"%d bytes altongether\n",size);
		write(s,buffer,strlen(buffer) + 1);
	}
}

int main()
{
	int ss,sc;
	struct sockaddr_in server_sock;
	struct sockaddr_in client_sock;

	pid_t pid;
	int err;

	ss = socket(AF_INET,SOCK_STREAM,0);
	if(ss < 0)
	{
		printf("sock build error");
		return 1;
	}

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

	err = bind(ss,(struct sockaddr *)&server_sock,sizeof(server_sock));
	if(bind < 0)
	{
		printf("bind error");
		return 2;
	}

	err = listen(ss,_BACKLOG_);
	if(err < 0)
	{
		printf("listen is error");
		return 3;
	}

	while(1)
	{
		socklen_t len = sizeof(struct sockaddr);

		sc = accept(ss,(struct sockaddr *)&client_sock,&len);

		if(sc < 0)
		{
			continue;
		}

		pid = fork();

		if(pid == 0)
		{
			process_conn_server(sc);
			close(ss);
		}
		else
		{
			close(sc);
		}

	}
	return 0;
}

对于这些代码,首先来了解一下服务器要是先套接字所需要进行的函数调用过程,然后在来讨论一些效率和安全性的问题。

首先我们需要创建网络插口函数socket():

domain设置网络通信域,在以太网中使用AF_INET。

type是设置套接字通信的类型,TCP是面向字节流传输,所以使用SOCK_STREAM。

protocol通常设置为0;

对于这个函数而言,他就是为了我们的通信而打开一个文件描述符,调用成功就返回文件描述符方便数据传输,失败就返回-1.同时传出错误值。

在socket编程中的这一套函数中,都进行了底层的封装,提供了相对的借口,

我们来看一下socket()的内核实现:

用户调用socket()后,

系统调用sys_socket,其中

  1. 生成内核socket结构。
  2. 与文件描述符绑定。将绑定文件描述符值传给应用层。

当建立套接字文件描述符成功后,就需要对套接字进行地址和端口绑定,这时候就是bind()函数,同时我们需要创建一个sockaddr_in 的结构体来进行绑定。

来看一下函数:

第一个参数就是我们调用socket所返回的文件描述符,第二个就是我们所创建的sockaddr_in结构体,当然,我们传参数的时候我们需要进行强制类型转换,第3个参数是我们所设置结构体的长度,同时也是输入数据,也是输出数据。

当bind返回0表示绑定成功,返回-1表示绑定失败。

然后我们还是看一下他的内核调用:

然后就可以进入监听状态,函数listen()用来初始化服务器可连接队列,服务器统一时间仅能处理一个客户端连接,当多个客户端的连接请求同时到来的时候,需要排入等待队列,一个一个处理。

listen函数:

sockfd代表文件描述符,

backlog代表等待队列的长度,成功返回0,失败返回-1.

listen内核:

图片出自Linux网络编程。

然后当我们设置监听理由就是accept()等待连接,然后我们连接成功后就会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以由这个新的描述符获得,然后可以通过write和read来实现数据传送。

看一下accept()函数:

函数参数中,

sockfd是创建的一个socket,这个socket是和listen用同一个socket,因为是送监听处得到请求连接;

addr是用于描述请求连接一方的网络地址信息结构体的指针;

addrlen是上述结构体的大小;

函数成功会返回有效的接收到的socket描述符,失败返回-1并置错误码;

内核调用:

好了这就是我们的服务端,他连接建立。

下面我们来看一下客户端的编写,代码:

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

#define _PORT_ 8888

void process_conn_client(int s)
{
	ssize_t size  = 0;
	char buffer[1024];
	while(1)
	{
		size = read(0,buffer,1024);
		if(size > 0)
		{
			write(s,buffer,size);
			size = read(s,buffer,1024);
			write(1,buffer,size);
		}
	}
}

int main()
{
	int s;

	struct sockaddr_in server_sock;

	s = socket(AF_INET,SOCK_STREAM,0);
	if(s < 0)
	{
		printf("sock error");
		return 1;
	}

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

	connect(s,(struct sockaddr *)&server_sock,sizeof(server_sock));
	process_conn_client(s);
	close(s);

	return 0;
}

对于客户端而言,我们只需要连接上我们的服务端就好了,所以只需要关注一个函数,connect函数,然后我们看一下connect()函数:

函数参数中,

sockfd是连接方创建的一个socket文件描述符;

addr因为是连接请求方,所以是远端要接收连接请求一方的网络socket地址信息;

addrlen是上述网络地址信息结构体的大小;

函数成功返回0,失败返回-1并置错误码;

他的内核实现:

对于客户端和服务端而言,我们的编程已近实现了,然后我们需要考虑的是几个关于服务端的性能问题:

在我所编写的代码中,我采用的是多进程的方式,每当我们建立连接后就fork一个子进程,子进程中进行数据传输,然后我们父进程就关闭accept所产生的文件描述符,子进程关闭不需要的监听的文件描述符。

在多进程中,我们需要注意到的是我们使用的是阻塞式的I/O模型,我们父进程中如果调用wait函数,waitpid函数进行等待总会比较浪费资源效率。

而且对于waitpid的非阻塞等待还存在一个问题,当我们需要释放子进程资源的时候,但是出现以下场景:

一个客户端连接进来,然后父进程进行非阻塞的等待,但是若以后都没有连接,将会卡在accept处,导致僵尸进程的产生。所以这种方式是存在问题的,所以有一种解决办法,就是利用子进程结束时返回的SIGCHLD信号来捕捉进行自定义的子进程资源释放,

还有一种方式就是多线程的编程,调用线程为分离状态。

        pthread_t tid;//创建出一个线程
        pthread_create(&tid, NULL, accept_fun, (void *)accept_sock);
        pthread_detach(tid);//将线程设置成分离状态,结束后不必等主线程回收资源
void* accept_fun(void *sock)
{
    int accept_sock = (int)sock;
    char *buf[1024];
    while(1)
    {
        memset(buf, ‘\0‘, sizeof(buf));
        size_t size = read(accept_sock, buf, sizeof(buf)-1);
        if(size < 0)
        {
            perror("read");
            break;
        }
        else if(size == 0)
        {
            printf("client is out...\n");
            break;
        }
        else
            printf("client# %s\n", buf);
    }
 
}

当然了,无论是多线程方式还是多进程方式,他总会出现性能上的低效率,因为一个线程/进程只能够处理一个连接的话是十分低效的,所以我们需要继续学习关于I/O的几种模型,使用多路复用状态的模型去进行服务器端的编写。

时间: 2024-10-22 17:17:44

socket编程:简单TCP服务器/客户端编程的相关文章

运用socket实现简单的服务器客户端交互

Socket解释: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意思.通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信.在Internet上的主机一般运行了多个服务软件,同时提供几种服务.每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务.Socket正如其英文原意

socket编程:简单UDP服务器/客户端编程

对于之前的TCP协议而言,他是可靠的字节流传输,而在socket编程中,在不需要保证数据传输正确安全的情况下.或者由用户自己完成传输确认情况/服务端客户端自己实现数据传输.套接字编程也提供了UDP协议的方法. 基于UDP(不是面向连接)的socket编程,分为客户端和服务器端. 客户端的流程如下: (1)创建套接字(socket) (2)和服务器端进行通信(sendto) (3)关闭套接字 因为在socket编程中,UDP是针对数据报的数据传输,所以socket专门定义了UDP所使用的函数接口.

Socket网络编程--简单Web服务器(1)

这一次的Socket系列准备讲Web服务器.就是编写一个简单的Web服务器,具体怎么做呢?我也不是很清楚流程,所以我找来了一个开源的小的Web服务器--tinyhttpd.这个服务器才500多行的代码,使用C语言.这一小节就不讲别的内容了.就对这个程序进行一些注释和讲解了. 主函数: 1 int main(void) 2 { 3 int server_sock = -1; 4 u_short port = 0; 5 int client_sock = -1; 6 struct sockaddr_

Socket网络编程--简单Web服务器(6)

本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息,例如一个配置类的一个属性为PAGE404的字符串,该字符串保存一个文件地址,然后我们的Page_404函数就可以用access判断PAGE404这个字符串对应的文件是否存在,如果存在那么如果是404页面那么就cat这个文件,而不是默认的404函数里面的页面.还有个端口什么的都是通过一个类似宏定义一

[python网络编程]利用socket编写简单的服务器

利用socket编写简单的服务器 步骤解析 建立socket对象 这一步跟我们上次使用socket做客户端是一样的,都需要有一个socket连接 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 设置socket选项(setsockopt) 选项介绍setsockopt(set socket option),参数设置格式:setsockopt(level,optname,value) 各选项的关系:1.level:level的设定决定了op

Socket实现简单Web服务器

上一篇博客中介绍了怎样使用socket访问web服务器.关键有两个: 1)熟悉Socket编程: 2)熟悉HTTP协议. 上一篇主要是通过socket来模拟浏览器向(任何)Web服务器发送(HTTP)请求,重点在浏览器端.本篇博客则反过来讲一下怎样使用socket来实现Web服务器,怎样去接收.分析.处理最后回复来自浏览器的HTTP请求. HTTP协议是浏览器和Web服务器都需要遵守的一种通信规范,如果我们编写一个程序,正确遵守了HTTP协议,那么理论上讲,这个程序可以具备浏览器.甚至Web服务

C#中使用Socket实现简单Web服务器

原文地址:https://www.cnblogs.com/mq0036/p/6656888.html 最近有个web的小项目,但公司的电脑无法安装IIS,所以就想自己来实现个Web server服务器,原本想了下,也就是socket处理http请求,于是就在博客园中搜索了"socket实现web server",结果还真搜索到一些文章,于是从中找了几个做参考,如下: C#中使用Socket实现简单Web服务器 C#中使用Socket模拟请求Web服务器过程 C#中自己动手创建一个Web

Windows Socket 编程_ 简单的服务器/客户端程序

转载自:http://blog.csdn.net/neicole/article/details/7459021 一.程序运行效果图 二.程序源代码 三.程序设计相关基础知识 1.计算机网络    2.IP地址    3.协议    4.网络体系结构    5.TCP/IP体系结构与特点    6.客户机/服务器模式    7.TCP/IP特点    8.套接字的引入    9.面向 连接/无连接 的套接字的系统调用时序图/流程图 一.程序运行效果图 二.程序源代码 [cpp] view pla

2015/12/14 Python网络编程,TCP/IP客户端和服务器初探

一直不是很清楚服务器的定义,对于什么是服务器/客户端架构也只有一个模糊的感觉.最近开始学习,才明白一些什么服务器和客户端的关系. 所谓的服务器,就是提供服务的东西,它是一个硬件或者软件,可以向一个或者多个客户端提供所需要的服务.它存在的目的就是等待客户的请求,然后给客户服务,再接着等待请求. 而客户端,就来连上一个服务器,提出自己的请求,然后等待获得反馈. 比如说,打印机就是一个服务器的例子,与之相连的计算机就是客户端,通过网络连接打印机后,给它提出服务需求(打印)和传输数据(传输内容),然后打