socket通信:客户端和服务器的简单实现

什么是socket?

socket最开始的含义是一个地址和端口对(ip, port)。Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求

  • socket地址API:它唯一的表示了使用tcp通信的一端,也可以将其理解成socket地址。
  • socket基础API:socket的主要API都定义在sys/socket.h头文件中,包括创建socket,命名socket,监听socket,接收连接,发起连接,读写数据等等。

主机字节序和网络字节序

现代cpu的累加器一次都能装载4字节,即一个整数。那么这四个字节在内存中的排列顺序就会影响它被累加器装载成的整数的值。这就是字节序问题。字节序分为大端字节序和小端字节序。

  • 大端字节序:一个整数的高位字节存储在内存中的低地址处,低位字节存储在内存中的高地址处。
  • 小端字节序:一个整数的高位字节存储在内存中的高地址处,低位字节存储在内存中的高地址处。

现代PC机大多数采用小端字节序,因此小端字节序又被称为主机字节序。当格式化的数据在两台使用不同字节序的主机之间直接传递时(不经过字节序的转换统一),接收端必然错误的解释之。因此人们提出了一种解决办法:发送端总是把要发送的数据转化成大端字节序数据后再发送,而接收端就默认接收到的数据都是采用大端字节序,再根据自身的字节序决定是否对齐进行转化。因此大端字节序也被称为网络字节序

字节序的转换函数

Linux系统提供了如下4个函数来完成主机字节序和网络字节序的转化。

#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);//IP地址从主机字节序转化成网络字节序
unsigned short int htons(unsigned short int hostport);//端口号从主机字节序转化为网络字节序
unsigned long int ntohl(unsigned long int netlong);//IP地址从网络字节序转化为主机字节序
unsigned short int ntohs(unsigned short int netshort);//端口号从网络字节序转化为主机字节序

字节序和socket通信之间的关系:我们知道socket套接字就是两个主机之间通信的方式,需要经过传输层和网络层协议(tcp/ip)来进行数据的传输,而这里就会牵扯到字节序的问题。需要进行字节序的转化才能将正确的数据传出去和接收到。

专用socket地址

TCP/IP协议族有sockaddr_in这个用于IPV4的专用socket地址结构体

struct sockaddr_in
{
    sa_family_t sin_family;        /*地址族:AF_INET*/
    u_int16_t sin_port;            /*端口号:要用网络字节序表示*/
    struct in_addr sin_addr;       /*IPV4地址结构体,见下面*/
};
struct in_addr
{
    u_int32_t s_addr;        /*IPV4地址,要用网络字节序表示*/
};

注意:所有专用socket地址类型的变量在实际使用时都需要强制转化为通用socket地址类型sockaddr,因为所有socket编程接口使用的地址参数类型都是sockaddr

IP地址转化函数

人们通常会使用可读性好的点分十进制字符串表示IPv4地址,但是在编程过程中我们要先将其转化成整数才能使用。我们经常使用下面这个函数将点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的ipv4地址。

in_addr_t inet_addr(const *strptr);    /*strptr表示点分十进制字符串形式的IPv4地址*/

 

一·创建socket:

linux/unix系统下以切皆文件,socket也不例外,它就是一个可读可写,可控制,可关闭的文件描述符。

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);    /*成功返回一个文件描述符,失败返回-1*/
  • domain:该参数告诉系统使用哪个底层协议族,对于tcp/ip协议族而言,该参数应该设置为PF_INET(用于IPv4)或者PF_INET6(用于IP v6)。
  • type:该参数指定服务类型,主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报服务)
  • protocol:该参数是在前两个参数构成的协议集合下,再选择一个具体的协议。不过这个值通常都是唯一的。几乎在所有情况下,我们都应该把它设置为0.表示使用默认协议

二·命名socket

将一个socket与socket地址(上面的sockaddr_in结构体)绑定称为给socket命名。在服务器端我们通常要命名socket,只有命名之后客户端才知道怎样连接它。而客户端通常不需要命名socket,而是采用匿名方式。下面这个系统调用专门用来命名socket:

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
/成功返回0,失败返回-1*/
  • sockfd:文件描述符
  • my_addr:socket地址
  • addrlen:socket地址长度

三·监听socket

socket被命名之后,还不能马上接收客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接:

#include<sys/socket.h>
int listen(int sockfd, int lacklog);    /*成功返回0,失败返回-1*/
  • sockfd:指定被监听的socket。
  • backlog:提示内核监听队列的最大长度。如果超过该长度,服务器则不接受新的客户连接
  • 注意:在内核版本2.2之前的Linux中,backlog参数指的是所有处于半连接状态和完全连接状态的socket上限,2.2版本之后,只表示处于全连接状态的socket上限。典型值为5.

四·发起连接

一般情况下,客户端需要通过如下系统调用来主动与服务器建立连接:

#include<sys.types.h>
#include<sys/socket.h>
int connect(int sockfd, const sockaddr* serv_addr, socklen_t addrlen);
/*成功返回0,失败返回-1*/
  • sockfd:由socket系统调用返回的一个socket。
  • serv_addr:服务器监听的socket地址。
  • addrlen:指定该地址的长度。

五·接受连接

我们使用下面这个系统调用来从listen的监听队列中接受一个链接:

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
/*成功返回一个新的连接socket,失败返回-1*/
  • sockfd:执行过listen系统调用的监听socket。
  • addr:用来获取被接受的远端socket地址。
  • addelen:指向的内存中存储着addr的大小

accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接的客户端进行通信。

六·数据读写

tcp数据读写

对于文件的读写操作read和write也适用于socket。但是socket编程接口提供了专门用于socket数据读写的系统调用,他们增加了对数据读写的控制,其中tcp流数据读写的系统调用是:

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);    /*接收数据*/
ssize_t send(int sockfd, const void *buf, size_t len, int flags);    /*发送数据*/
  • sockfd:recv读取sockfd上的数据,send往sockfd上写数据
  • buf:指定读写缓冲区的位置
  • size_t:指定读写缓冲区的大小
  • flags:为数据收发提供了额外的控制,它可以取下面中的一个或者几个的逻辑或。

udp数据读写

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr*                    src_addr, socklen_t* len);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, struct sockaddr* desr_addr, socklen_t addrlen);

  • sockfd:recvfrom读取sockfd上的数据,sendto往sockfd上写入数据。
  • buf:recvfrom指定读缓冲区的位置,sendto指定写缓冲区的位置。
  • len:recvfrom指定读缓冲区的大小,sendto指定写缓冲区的大小。
  • src_addr:因为udp通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,也就是该参数所指的内容
  • dest_addr:指定接收端的socket地址。
  • addrlen:分别指定发送端和接收端socket地址的大小
  • flags:每个函数中的flags都与send/recv中的flags含义相同

七·关闭socket

在程序结束时,我们使用close系统调用来关闭socket

int close(sockfd);

编程实例

tcp客户端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/un.h>



int main()
{
	int sockfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in ser,cli;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	//int sockfd = 0;
	int err = connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));
	if(err == -1)
	{
		printf("connect error!\n");
	}

	while(1)
	{
		printf("please input:");
		fflush(stdout);

		char buff[128] = {0};
		fgets(buff, 127, stdin);
		buff[strlen(buff)-1] = 0;
		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
		
		err = send(sockfd, buff, strlen(buff), 0);
		assert(err != -1);

		char recvbuff[128] = {0};
		err = recv(sockfd, recvbuff, 127, 0);
		assert(err = -1);
		printf("%s\n",recvbuff);

	}
	close(sockfd);
	return 0;
}

tcp服务器

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<netinet/in.h>
#include<arpa/inet.h>


int main()
{
	int sockfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);
	
	struct sockaddr_in ser,cli;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int err = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
	assert(err != -1);

	err = listen(sockfd, 5);
	assert(err != -1);

	while(1)
	{
		int len = sizeof(cli);
		int c = accept(sockfd, (struct sockaddr*)&cli, &len);
		if(c == -1)
		{
			printf("accept error\n");
			continue;
		}
		while(1)
		{
			char buff[128] = {0};
			err = recv(c, buff, 127, 0);
			if(err <= 0)
			{
				printf("client disconnect!\n");
				break;
			}
			printf("%s\n",buff);

			err = send(c, "ok", 2, 0);
			if(err == -1)
			{
				printf("error\n");
			}
		}
	}
	close(sockfd);
	return 0;
}

udp客户端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/un.h>


int main()
{
	int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
	assert(sockfd != -1);
	
	struct sockaddr_in ser,cli;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	while(1)
	{
		char buff[128] = {0};
		printf("please input:");
		fflush(stdout);
		fgets(buff, 127, stdin);
		buff[strlen(buff)-1] = 0;
		if(strcmp(buff,"end") == 0)
		{
			break;
		}
		int err = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&ser, sizeof(ser));
		assert(err != -1);

		char recvbuff[128] = {0};
		int len = sizeof(cli);
		err = recvfrom(sockfd, recvbuff, 127, 0, (struct sockaddr*)&cli, &len);
		assert(err != -1);

	}
	close(sockfd);
	return 0;
}

udp服务器

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/un.h>

int main()
{

	int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
	assert(sockfd != -1);
	
	struct sockaddr_in ser,cli;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int err = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(err != -1);

	while(1)
	{
		char buff[128] = {0};
		int len = sizeof(cli);
		err = recvfrom(sockfd, buff, 127, 0, (struct sockaddr*)&cli, &len);
		if(err <= 0)
		{
			continue;
		}
		printf("%s\n",buff);
		
		err = sendto(sockfd, "ok", 2, 0, (struct sockaddr*)&cli, sizeof(cli));
		assert(err != -1);
	}
	close(sockfd);
	return 0;
}



猜你喜欢

转载自blog.csdn.net/Mr_H9527/article/details/84391005