套接字与文件描述符

 

TCP服务器端:

int socket(int domain , int type , int protocol)
domain(协议族):常用的协议族便是IPV4(PF_INET), IPV6(PF_INET6),本地通信协议的UNIX族(PF_LOCAL)
type:数据传输类型;典型数据传输类型:面向连接的套接字(SOCK_STREAM),面向消息的套接字(SOCK_DGRAM)
protocal:具体协议;
返回套接字文件描述符,在linux中,不区分套接字和文件,统一用文件描述符来描述;

TCP与UDP的区别:

  1. TCP是面向连接,UDP是无连接的传输
  2. TCP保证了数据传输的正确和有序,而UDP不保证
  3. TCP数据传输是无边界的,也就是流模式(待查),UDP传输是有边界的,采用数据报模式(待查)
  4. TCP需要更多的系统资源
int bind(int sockfd , const struct sockaddr* servaddr , socklen_t addrlen);
给套接字分配IP地址和端口号,将套接字和相应的IP地址和端口号绑定,失败返回-1;当没有给套接字绑定IP地址和端口号(端口号指定为0)时,bind函数被调用时会分配一个临时端口号与套接字进行绑定,但是IP地址会当TCP连接建立(客户端目的IP地址)或者在此套接字上发送UDP数据报时才会绑定IP地址;当IP地址指定为通配地址(INADDR_ANY)时,由内核来选定IP地址)
int listen(int fd , int backlog)
将套接字变为可接受连接状态;内核为监听套接字维护两个队列,一个是未完成连接队列,该队列中的每一项为客户端发送的SYN字节,另一个是已完成连接队列,存储已完成连接的客户端套接字,当accept调用时,就从该队列的队头返回,当该队列为空的时候,进程进入睡眠,等待被唤醒
int accept(int sockfd , struct sockaddr* addr , socklen_t* addrlen) ;
接受连接,返回一个全新的套接字,称为“已连接套接字”,由内核创建的一个全新的套接字,自动完成与对应的客户端套接字的连接(三路握手),由这个全新的套接字与客户端套接字进行通信

TCP客户端:

int connect(int sockfd , struct sockaddr* servaddr, socklen_t addrlen)
connect函数完成两件事,如果套接字没有被绑定IP地址和端口号,内核会分配一个临时端口号,并与套接字进行绑定。如果是TCP套接字则会向服务器发起连接,进行三路握手,连接成功或失败才会返回;

基于TCP的服务器端/客户端函数调用过程:

TCP三路握手和四次挥手:

TCP为什么要三次握手?

  因为客户端和服务器之间要互相同步各自的初始信号,所以每一个SYN都需要一个ACK,因为服务器端的SYN和ACK可以合并发送,所以需要三次;

TCP为什么要四次挥手?

  因为客户端和服务器之间是全双工连接,每一个FIN都需要一个ACK。因为服务器端的FIN和ACK不能合并的原因是在某些情况下,客户端不再向服务器端发送数据后,服务器端可能还要向客户端发送数据,所以不能合并,所以需要四次。

为什么执行主动关闭的那端需要进入TIME_WAIT状态?

  首先ACK数据包可能在网络中丢失,所以被动关闭那端可能需要重传FIN,这就需要主动关闭那端维持状态准备重传ACK;另一个原因是网络中可能存在旧连接的一些迷路的重复分组,处于TIME_WAIT状态下的连接不能建立新的化身,TIME_WAIT状态会持续2个MSL时间,足够使得旧连接的重复分组消失在网络中,避免新连接出现旧连接的重复分组;

TCP套接字的I/O缓冲

  1. 输入缓冲和输出缓冲单独存在
  2. I/O缓冲在创建套接字的时候自动生成
  3. 关闭套接字后仍会继续传递输出缓冲中的数据
  4. 关闭套接字后会丢失输入缓冲中的数据
tcp_server.c

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

#define BUF_SIZE 1024

void error_handling(char* m) ;

int main(int argc , char* argv[])
{
     int listen_sock , connd_sock , readlen;
     struct sockaddr_in serv_addr , clnt_addr ;
     socklen_t addr_len;

     if(argc != 2) error_handling("argc error") ;

     listen_sock = socket(PF_INET , SOCK_STREAM , 0) ;
     if(listen_sock == -1) error_handlind("socket error") ;

     serv_addr.sin_family = AF_INET ;
     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY) ;
     serv_addr.sin_port = htons(atoi(argv[1]));

     if(bind(listen_sock , (struct sockaddr*)&serv_addr , sizeof(serv_addr)) == -1)
         error_handling("bind error") ;

     if(listen(listen_sock , 5) == -1)  error_handling("error listen") ;

     addr_len = sizeof(clnt_addr) ;
     for(int i = 0 ; i < 5 ; i++)
     {
          connd_sock = accept(listen_sock , (struct sockaddr*)&clnt_addr , &addr_len) ;
          if(connd_sock == -1) error_handling("error accept") ;

          while(readlen = read(connd_sock , message , BUFSIZE) > 0)
              write(connd_sock , message , readlen) ;

          close(connd_sock) ;
     }

     close(listen_sock);
     return 0 ;
}        
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUFSIZE 1024

void error_handling(char* m);

int main(int argc , char* argv[])
{
    int clnt_sock;
    struct sockaddr_in serv_addr;

    if (argc != 3) error_handling("error argc");

    clnt_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (clnt_sock == -1) error_handling("error socket");

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(clnt_sock, (struct sock_addr*) & serv_addr, sizeof(serv_addr)) == -1) error_handling("connect error");

    char message[BUFSIZE];
    int sendlen , recvlen , readcnt;
    while (1)
    {
        fputs("input message(q to quit): ", stdout);
        fgets(message, BUFSIZE, stdin);

        if (message == "q\n" || message == "Q\n") break;

        sendlen = write(clnt_sock, message, sizeof(message));
        recvlen = 0;

        while (recvcnt = read(clnt_sock, message + recvlen, BUF_SIZE - 1) > 0)
        {
            recvlen += recvcnt;
            if (recvlen >= sendlen) break;
        }
        message[recvlen] = 0;
        fputs(message, stdout);
    }

    close(clnt_sock);

    return 0;
}

原文地址:https://www.cnblogs.com/mychen06/p/12580471.html

时间: 2024-11-04 00:33:27

套接字与文件描述符的相关文章

Linux 套接字与文件描述符

端口和套接字,用于确定指定主机上的哪个本地进程使用了哪个协议和哪台远程主机上的哪个进程进行了通信.端口和套接字的使用可以基于以下几点: ①为每个应用过程分配一个过程标识符(Process ID),每次启动一个进程时,这个ID都可能是不同的. ②进程ID因操作系统平台不同而不同,因而它们是不统一的. ③一个服务器过程能够同时与多个客户连接,因而简单的连接标识符不可能是唯一的. 端口和套接字概念提供了一种以统一的方式唯一地标识连接以及参与连接的程序和主机的方法,而不管特定的过程ID. (1)端口 

linux套接字或者文件描述符的未读取得字节数FIONREAD,MSG_PEEK标志

FIONREAD,就是返回缓冲区有多少字节.输入有个输入缓冲区,用int nread;ioctl(0,FIONREAD,&nread); 能得到缓冲区里面有多少字节要被读取.值放在 nread里面了. 然后就可以 read 了.nread = read(0,buffer,nread); ============================================================================ MSG_PEEK标志可以用来读取套接字接收队列中可读的数据

通过UNIX域套接字传递文件描述符

传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程序设计范式 ??开发一个服务器程序,有较多的的程序设计范式可供选择,不同范式有其自身的特点和实用范围,明了不同范式的特性有助于我们服务器程序的开发.常见的TCP服务器程序设计范式有以下几种: 迭代服务器 并发服务器,每个客户请求fork一个子进程 预先派生子进程,每个子进程无保护地调用accept 预先

套接字的文件描绘符加入到readfds集结

那么你敲的键应当是回车 RETURN否则不论怎么它都会超时.假定你一个 linebuffered终端上.> 而别的一些则不能.查验早年或许要先看看本体系的manpage如今或许回以为这就是数据报套接字上等候数据的方法--对 或许是有些 Unix体系能够按这种方法.> 能够经过将该中来看是不是有新的联接.毕竟一件对于 select作业:假定你有一个正在侦听 listen套 接字.> 这就是对于函数select要讲的悉数的东西. volumI-IIIbyDouglaE.Comerand In

套接字文件描述符消耗小细节分析

套接字是通信端点的抽象.正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字.套接字描述符在UNIX系统中被当作是一种文件描述符.事实上,许多处理文件描述符的函数(read和write)可以用于处理套接字描述符. ——<unix环境高级编程> WEB应用客户端(浏览器或APP等)与服务端的通信大多数是通过HTTP或HTTPS协议进行的.而套接字(Socket)应该算是OSI七层网络模型中应用层与传输层直接的抽象层,它是面向应用层的一个编程接口.而HTTP/HTTPS则位于应用层,就是通

Linux中的文件描述符与打开文件之间的关系

1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个新的文件,它的文件描述符会是3.POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号

每天进步一点点——Linux中的文件描述符与打开文件之间的关系

转载请说明出处:http://blog.csdn.net/cywosp/article/details/38965239 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个

UNIX进程之间传递文件描述符recvmsg与sendmsg

socketpair: 功能:创建一个全双工的流管道 原型 int socketpair(int domain, int type, int protocol, int sv[2]); 参数 domain: 协议家族 type: 套接字类型 protocol: 协议类型 sv: 返回套接字对 返回值:成功返回0:失败返回-1 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int

关于Linux文件描述符的笔记

当某个程序打开文件时,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符.所谓的文件描述符是一个低级的正整数.最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应.因此,函数 scanf() 使用 stdin,而函数 printf() 使用 stdout.你可以用不同的文件描述符改写默认的设置并重定向进程的 I/O 到不同的文件. 首先说什么是文件描述符,它有什么作用? 文件描述符是一个简单的整数,用以标明每一个被