socket何许人也?

之前我们讲过TCP/IP协议,这时属于传输层的东西,只是讲了概念。那么怎么来使用呢?所以socket它在万众期待下来了。

socket是啥?

在编程界或者说计算机界,我们管它叫套接字,是计算机之间进行通信的一种约定或方式。通过它,两个计算机可以实现通信。
linux下的一切文件来讲,socket其实看作一个特殊的文件,socket函数就是对文件的操作。
总之,socket就是一个连接和传输的工具可以看作是。

socket通信

之前我们讲过,网络间进程通信时,通过IP地址识别主机,通过协议加端口识别进程。
socket就是用这样的方式进行通信的。它可以看作是装载了这个三元组的中间件。
主要是两种传输方式:
SOCK_STREAM:面向连接的传输方式。数据准确无误到达对方,TCP连接的可靠性实现。
SOCK_DGRAM:无连接的传输方式,只传输不校验。所以效率比连接的快。

怎么用?

光说不练假把式,那么我们怎么使用socket呢?
在这里插入图片描述
这是一个客户端和服务器实现tcp通信的流程图。我们就照着这个,一步一步进行编程。
创建socket

//int socket(int af, int type, int protocol)为函数原型
int fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

第一个参数是int af,是IP地址类型,AF_INET为IPv4。
第二个参数就是传输模式int type,上面说的stream和dgram。
第三个是传输协议,IPPROTO_TCP。
绑定bind

//int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 函数原型
//先定义一个结构体变量
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));//init
addr.sin_family=AF_INET;//IPv4
addr.sin_port==htons(1234);//端口
addr.sin_addr.s_addr=inet_addr("127.0.0.1");//此IP可以记住,本机IP表示法
int bd=bind(fd,(struct sock_addr*)&addr,sizeof(addr));//bind

可以通过bind原型看到:
第一个参数为sock文件描述符
第二个addr是一个结构体的指针,所以我们要先定义这样一个结构体。
后面是结构体的大小,sizeof可求得。
连接connect:

//int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); 原型
int ct=connect(fd,(struct sock_addr*)&addr,sizeof(addr));

listen和accept:
服务器端在bind后,需要调用listen进入被动监听状态,当没有连接请求时,套接字睡眠状态,接到时,才响应请求。并调用accept,这样便可以对客户端进行响应。

int listen(int sock, int backlog); //backlog为请求队列的最大长度

何为请求队列?
当套接字在处理客户端请求时,如果来了新连接请求,此时不能马上处理,所以需要有缓存区,放进缓存区,等现在请求处理完毕后,再从缓存区读取出来处理。当不断有新请求到来时,我们就把他们按先后顺序存起来,也就是队列。backlog即是设置此队列的长度。
请求队列满时,新的请求进来时就无法进入队列,也就是无法连接,,客户端便会收到错误回报。
当监听到请求时,便进行连接。

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //阻塞等待

里面参数和前面的一致,需要注意的是返回值,此处返回一个新的套接字。也就是说,后续和客户端通信使用新的这个套接字。
数据接收和传递
write和read操作可以对套接字进行读写。服务器write进套接字,客户端收到,read出就是一次通信。

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

fd为文件描述符,buf为写入数据的缓存区地址,所以要初始化一个buf,后面则是写入的字节数。
返回值:成功时是写入的字数,失败是-1.

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

同样,read将fd文件中读取nbytes个字节并保存进缓存区。
socket缓冲区及阻塞
socket在创建之时,便会创建两个缓存区,输入和输出。
前面讲TCP时说过会将数据封装进行发送,写一个发一个多浪费,所以socket会先将数据写进缓存区。TCP将缓存区数据发送即可。同样读操作也是如此。
可以这么理解,应用程序和TCP之间有一个缓存区,这就是socket的缓存区,TCO传输的是缓存区内的数据,并不是直接从应用程序读取。
所以:每个套接字都会有隐性的两个缓存区单独存在,在创建socket时同步创建,在关闭套接字后,缓存区的数据可以继续传输输出缓存区的遗留字节,但是输入缓存区的会丢失。
write/send函数:

  • TCP在传输数据时,输出缓存区此时不可写入。函数阻塞直到数据发送完毕。
  • 当写入的数据大于缓存区长度时,会分批写入。
  • 缓存区的数据发送操作是TCP协议的事,所以缓存区内的数据怎么分怎么发是TCP的事。
  • 当所有数据写入完毕时,函数返回。

recv/read函数

  • 典型的阻塞函数,如果缓存区有数据,则读取,否则阻塞到有数据到来;
  • 如果读取长度小于缓存区长度,那么便会数据积压,直到函数一次次操作读完
  • 直到读取完毕,函数返回,否则阻塞。

最后用一个实例来贯通一下:

//server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc,const char* argv[])
{
    if(argc != 3)
    {
        printf("Usage:%s [loacl_ip] [loacl_port]\n",argv[0]);
        return 1;
    }
    
	int fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd<0)
	{
		perror("socket error!");
		exit(1);
	}
	
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr=inet_addr(argv[1]);
	addr.sin_port=htons(atoi(argv[2]));
	
	int bd=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
	if(bd<0)
	{
		perror("bind error!");
		exit(2);
	}

	int li=listen(fd,5);
	if(li<0)
	{
		perror("listen error!");
		exit(3);
	}
	
	struct sockaddr_in client;
	socklen_t len=sizeof(struct sockaddr_in);
	
	while(1)
	{
		int sock=accept(fd,(struct sockaddr*)&client,&len);
		if(sock<0)
		{
			perror("accept error!");
			continue;
		}
		printf("client's ip:%s,port:%d\n",inet_ntoa(client.sin_addr),ntos(client.sin_port));
		char buf[1024];
		while(1)
		{
			ssize_t s=read(sock,buf,sizeof(buf)-1);
			if(s>0)
			{
				buf[s]=0;
				printf("client:%s",buf);
			}
			else
			{
				printf("read error!");
				break;
			}
		}
	}
	return 0;
}
//client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main(int argc,const char* argv[])
{
    if(argc != 3)
    {
        printf("Usage:%s [ip] [port]\n",argv[0]);
        return 0;
    }

    //创建一个用来通讯的socket
    int sock = socket(AF_INET,SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket error!");
        return 1;
    }

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(struct sockaddr_in);
    int ct = connect(sock, (struct sockaddr*)&server, len);
    if( ct < 0 )
    {
        perror("connect error!");
        return 2;
    }
    
    char buf[1024];
    while(1)
    {
        printf("send:");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        buf[s] = 0;
        write(sock, buf, s);
    }
    close(sock);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值