Socket学习

一、定义 什么是Socket

在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。

 

二、不得不说的TCP/IP协议

为什么学习Socket要说TCP/IP协议呢?它们之间好比送信的线路和驿站之间的关系,如果你想开一家驿站,你就得了解送信的各个细节。

不同于iso分7个层次,TCP/IP协议对其进行重新归类到了四个抽象层中:

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

传输层:TCP,UDP

网络层:IP,ICMP,OSPF,EIGRP,IGMP

数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的:

 

三、再看Socket

在上面的图中,我们并没有发现Socket的踪迹,我们只是知道Socket是在tcp/ip上的网络编程。看一下下面这张图:

 

我们发现在运输层和应用层之间,设计了一个Socket的抽象层,传输层的底一层的服务提供给Socket抽象层,Socket抽象层再提供给应用层。

在我们理解Socket编程怎么实现服务端与客户端通讯之前,我们先来了解一下TCP/IP之间是怎么通讯的。

在TCP/IP协议中,TCP协议通过三次握手来建立一个可靠的连接,大致如下图所示:

 

第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认

 

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

 

第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

 

接下来我们再说一说Socket是如何建立连接的。和TCP一样,Socket也定义的三次握手,大致过程如下图所示:

 

第一次握手:客户端需要发送一个syn j 包,试着去链接服务器端,于是客户端我们需要提供一个链接函数

 

第二次握手:服务器端需要接收客户端发送过来的syn J+1 包,然后在发送ack包,所以我们需要有服务器端接受处理函数

 

第三次握手:客户端的处理函数和服务器端的处理函数

 

      三次握手只是一个数据传输的过程,但是,我们传输前需要一些准备工作,比如将创建一个套接字,收集一些计算机的资源,将一些资源绑定套接字里面,以及接受和发送数据的函数等等,这些功能接口在一起构成了socket的编程。我们再用一张图来看Socket原理会清晰很多:

 

四、Socket编程会用到的API

之前我们说过,Socket是起源于Linux下的,也就是一切皆文件,是通过“打开-读/写-关闭”的模式来实现的,接下来我们介绍几个我们常用到的Socket API。

int socket(int domain, int type, int protocol);

根据指定的地址族、数据类型和协议来分配一个socket的描述字及其所用的资源。

domain:协议族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址

type:socket类型,常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等

protocol:协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

把一个地址族中的特定地址赋给socket

sockfd:socket描述字,也就是socket引用

addr:要绑定给sockfd的协议地址

addrlen:地址的长度

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

int listen(int sockfd, int backlog);

监听socket

sockfd:要监听的socket描述字

backlog:相应socket可以排队的最大连接个数 

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

连接某个socket

sockfd:客户端的socket描述字

addr:服务器的socket地址

addrlen:socket地址的长度

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

 TCP服务器监听到客户端请求之后,调用accept()函数取接收请求

sockfd:服务器的socket描述字

addr:客户端的socket地址

addrlen:socket地址的长度

ssize_t read(int fd, void *buf, size_t count);

读取socket内容

fd:socket描述字

buf:缓冲区

count:缓冲区长度

ssize_t write(int fd, const void *buf, size_t count);

向socket写入内容,其实就是发送内容

fd:socket描述字

buf:缓冲区

count:缓冲区长度

int close(int fd);

socket标记为以关闭 ,使相应socket描述字的引用计数-1,当引用计数为0的时候,触发TCP客户端向服务器发送终止连接请求。

 

最后我放一个大神的实例大家可以去实验一下:

服务器端:

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
 
using namespace std;
 
typedef unsigned char uchar;
 
int main()
{
	//build the socket for server
	int s = socket(AF_INET, SOCK_STREAM, 0);
	//bind the socket to address
	struct sockaddr_in adr_s;
	adr_s.sin_family = AF_INET;
	adr_s.sin_addr.s_addr = inet_addr("127.0.0.1");
	adr_s.sin_port =htons(1235);
	bind(s, (struct sockaddr *)&adr_s, sizeof(adr_s));
	//listen
	listen(s, 20);
	//accept the request from client
	//build the socket for client, system can bind local address to it automatically
	struct sockaddr_in adr_c;
	socklen_t c_size = sizeof(struct sockaddr_in);
	int c=  accept(s, (struct sockaddr *)&adr_c, &c_size);
 
	/*
	char str[] = "Hello World!";  // apply for a space for the received data
	int recv_result = write(c, str, sizeof(str));
	cout<<"recv_result is:"<<recv_result<<endl;
	*/
 
	char buffer[BUFSIZ];
	int len;
	
	while((len = recv(c, buffer, BUFSIZ, 0))>0)
	{
		buffer[len] = '\0';
		printf("recv string is:%s\n", buffer);
		if(send(c, buffer, len*sizeof(char), 0)<0)
			perror("send");
	}
	
	close(s);
	close(c);
	return 0;
 
}

客户端:

 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
# include <string.h>
#include <iostream>
#include <arpa/inet.h>
 
#include <unistd.h>
 
using namespace std;
 
typedef unsigned char uchar;
 
int main()
{
	//build a socket for the client
	int c = socket(AF_INET, SOCK_STREAM, 0);
	//build a socket for the server
	int s = socket(AF_INET, SOCK_STREAM, 0);	
	//the struct for the address of the server
	struct sockaddr_in adr_s;
	adr_s.sin_family = AF_INET;
	adr_s.sin_addr.s_addr = inet_addr("127.0.0.1");
	adr_s.sin_port =htons(1235);
	bind(s, (struct sockaddr *)&adr_s, sizeof(adr_s));
 
	//connect to the socket of the server.
	if(connect(c, (struct sockaddr*)&adr_s, sizeof(adr_s))<0)
		perror("connect");
 
	/*
	char buffer[40];
	read(c, buffer, sizeof(buffer)-1);
             printf("Message form server: %s\n", buffer);
             */
 
	char buffer[BUFSIZ];
	int len;
 
	while(1)
	{
		printf("input a string:");
		scanf("%s", buffer);
		if(send(c, buffer, strlen(buffer)*sizeof(char), 0)<0)
			perror("send");
		if((len = recv(c, buffer, BUFSIZ*sizeof(char), 0))<0)
			perror("recv");
		buffer[len] = '\0';
		printf("recv string is:%s\n", buffer);
	}
 
	close(c);
	return 0;
}


 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值