socket编程(TCP)

</pre><span style="font-family:SimHei;font-size:12px;"><pre code_snippet_id="521731" snippet_file_name="blog_20141116_1_4445628" name="code" class="objc">


流与文件的区别:
文件:i/o函数都是针对文件描述符的。打开一个文件,返回一个文件描述符,然后使用该文件描述符进行后续的i/o操作。
流:标准的I/O库是针对流的</span>。当打开一个流时,标准的I/O函数fopen返回一个指向FILE对象的指针。
该指针所指的对象包含I/O的文件描述符、指向文件缓冲区的指针、缓冲区长度、缓冲区字符数等。所以流可以认为是加缓冲区的文件。
我们常说unix中的一切就是文件。
也就是说,unix程序在执行任何形式的I/O时候,程序在读或者写一个文件描述符号。
文件描述符只是一个打开的文件相关联的整数。
打开的这个文件可能是一个网络链接、管道、终端、磁盘、socket或者file。


socket就是使用标准的unix文件描述符和其他程序进行通信的方式。
首先利用系统调用socket,返回网络套接字描述符(socket descriptor),然后利用这个套接字调用recv(read),send(write)接收、发送信息。
1.创建套接字 socket()函数,要创建一个套接字,可以调用socket函数:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

其中domain定义了通信类型AF_XXXX,其中前缀AF表示地址族(address family),XXXX表示用什么地址规则来生成地址。
描述
AF_INETIPV4因特网域(使用IP4地址规则生成地址)
AF_INET6IPV6因特网域
AF_UNIX/AF_LOCALUNIX域(使用本地规则生成地址)
AF_UNSPEC任何域

type定义套接字的类型,根据不同的套接字类型,提供对各网络层(网络层、传输层??)的访问接口。
描述
SOCK_DGRAM长度固定的、无链接的不可靠报文传递(传输层UDP)
SOCK_STREAM有序、可靠、双向的面向连接的字节流传递(传输层TCP)
SOCK_SEQPACKET长度固定的、有序、可靠的面向连接的报文传递(传输层 SCTP)
SOCK_RAWIP协议的数据报接口(提供对网络层的访问)

protocol通常设置为0,表示按照给定的域和套接字类型来选择默认协议。比如AF_INET通信域中套接字类型为SOCK_STREAM,那么他的默认协议就是TCP。

2.绑定 bind()函数
一旦创建了一个套接字,对于服务器端来说就需要绑定IP、端口,这样才能给客户端提供服务。
也就是使用bind函数把socket跟服务器端的socketaddr绑定在一起。
对于客户端来说,这一步可忽略。
bind函数如下:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
第一个参数为调用socket返回的套接字描述符。
第二个参数my_addr是执行数据结构sockaddr的指针,保存了服务器绑定的IP,端口信息。
第三个参数addrlen为sizeof(struct sockaddr)。

着重说一下第二个参数,地址标识了特定的通信域中套接字端点,地址格式其实是与特定的通信域有关
IPV4因特网域(AF_INET)定义的地址结构sockaddr_in
struct sockaddr_in {

  short int sin_family; /* 通信类型 */

  unsigned short int sin_port; /* 端口 */

  struct in_addr sin_addr; /* Internet 地址 */

  unsigned char sin_zero[8]; /*sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节*/
};

结构体in_addr定义:
typedef uint32_t in_addr_t;

struct in_addr {
    in_addr_t s_addr;
};

IPV6因特网域(AF_INET)定义的地址结构sockaddr_in6:
struct sockaddr_in6
 {
u_char sin6_len; 
u_char sin6_family; 
u_int16_t sin6_port; 
u_int32_t sin6_flowinfo; 
struct in6_addr sin6_addr;
u_int32_t sin6_scope_id;
}
struct in6_addr
{
u_int8_t __u6_addr8[16];
}

为使不同的格式地址被传入到套接字函数中,地址被强制转换为通用的数据结构sockaddr来表示
sockaddr定义:
struct sockaddr {

  unsigned short sa_family; /* 地址家族, AF_xxx */

  char sa_data[14]; /*14字节协议地址*/

};
所以如果使用的是sockaddr_in或者是其他的套接字地址结构,都会被强制转换为sockaddr结构传入。
例子:
#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#define MYPORT 3490

main()

{

  int sockfd;

  struct sockaddr_in my_addr;

  sockfd = socket(AF_INET, SOCK_STREAM, 0); /*域:ipv4 类型:流格式套接字 协议:tcp */

  my_addr.sin_family = AF_INET; /* 自定义地址协议族 */

  my_addr.sin_port = htons(MYPORT); /* 自定义端口号,并转换成网络字节顺序 。如果设置为0,标识随机使用一个没有使用的端口,不要绑定小于1024的端口号*/

  my_addr.sin_addr.s_addr = inet_addr("132.241.5.10");/*如果这个值被设置为INADDR_ANY就是泛指本机的意思,就是表示本机所有的IP,因为有些机子不止一块网卡。不然就填写本机的ip*/

  bzero(&(my_addr.sin_zero),; /* 设置其他空字节 */
  bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); /*将sockaddr_in强制转换为sockaddr*/
}

3.服务器端经过以上两步之后,客户端就可以建立链接。
首先通过系统调用 socket() 返回客户端的套接字文件sockfd描述符。
serv_addr 保存服务器端口和 IP 数据结构 struct sockaddr。
addrlen为 sizeof(struct sockaddr)。
sockfd没有绑定到一个地址,系统会给调用者一个绑定一个默认的地址。
connect()函数如下:
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

4.服务器端打开监听端口 listen()函数
服务器端打开监听端口之后,就可以监听来自客户端的请求。
listen函数:

int listen(int sockfd, int backlog);
sockfd 是调用 socket() 返回的服务器端套接字文件描述符。backlog 是在进入 队列中允许的连接数目。
就是说进入的连接且未被accept的数据小于backlog。大多数系统允许的数据为20。

5.接收客户端连接 accept()函数
服务器端之前创建了一个套接字sockfd,负责监听端口和接收客户端连接请求。
每次通过系统调用accept,生成一个新的套接字clifd,负责与客户端进行接收和发送信息。有多少个客户端被accept就会生成多少个这样的套接字
 accept()函数定义:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);   //这个addr是cliaddr
sockfd 是负责监听和建立连接的服务器端套接字,和 listen(),bind() 中一样的套接字描述符。
addr 是请求连接的客户端的sockaddr地址。

6 .发送和接收信息  send() and recv()函数这两个函数用于流式套接字或者数据报套接字的通讯。无连接的数据报套接字,使用sendto() 和 recvfrom()。

send() 函数定义:
int send(int sockfd, const void *msg, int len, int flags);
sockfd 是你想发送数据的套接字描述符(客户端是调用socket()返回的, 服务器端是调用 accept() 返回的。)
msg 是指向你想发送的数据的指针。len 是数据的长度。把 flags 设置为 0 就可以了。

recv() 函数很相似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
sockfd同上。buf 是要读的信息的缓冲。len 是缓 冲的最大长度。flags 可以设置为0。
 recv() 返回实际读入缓冲的数据的字节数。
或者在错误的时候返回-1, 同时设置 errno。

socket程序listen之后,不写accept函数,会是怎样一种情况?

socket程序listen之后,不写accept函数,会是怎样一种情况?


socket程序listen之后,不写accept函数,会是怎样一种情况?

今天看这这样的一个讨论
socket程序listen之后,不写accept函数,会是怎样一种情况?
http://bbs.csdn.net/topics/390566881#3329407-tsina-1-11824-f6e6a982b11a66de95e1610f5b2ad29a

答案大概可以用这样一句来回答:
If a con-nection  request  arrives with the queue full the client may receive an error with an indication of ECON-NREFUSED or, if the underlying protocol supports retransmission, the  request  may  be  ignored  so  that retries succeed.-------------listen队列一定会到达一种满的情况,如果底层协议不支持重传那么客户端会接受到ECON-NREFUSED的错误。


1.
TCP通信流程图

UDP通信流程图:
可见listen,accept,connet都是TCP/STCP专用的。


2.man listen:
listen就是在建立链接,且维护两个队列(incomming connecition/complete connection),accept从complete队列中取请求。不accept,队列一定会到满状态,此时底层协议不支持重传那客户端会接受到ECON-NREFUSED错误。


1).The listen() call applies only to sockets of type SOCK_STREAM(TCP) or SOCK_SEQ-PACKET(STCP)。
2).The behaviour of the backlog parameter on TCP sockets changed with Linux 2.2.  Now it specifies the queue length for completely established sockets waiting to be accepted, instead of  the  number  of  incomplete connection  requests. 参数backlog里面放的是已经建立了链接的请求,存放在一个队列中。
3).The  maximum  length  of  the  queue  for  incomplete sockets can be set using the tcp_max_syn_backlog sysctl.  When syncookies are enabled there is no  logical  maximum  length  and  this sysctl setting is ignored.  tcp_max_syn_backlog里面当的是未建立好链接的请求,存放在另一个队列中。
什么叫做未建立好的链接:
 The maximum number of queued connection requests which have still not received an acknowledgement from the connecting client.  




http://bbs.csdn.net/topics/390566881#3329407-tsina-1-11824-f6e6a982b11a66de95e1610f5b2ad29a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值