网络编程之套接字

来源:互联网 发布:安装ubuntu没有win引导 编辑:程序博客网 时间:2024/05/17 16:01

看完了《TCP/IP网络编程》这本书,现在分批做点总结:  

首先要理解什么是套接字: 

百度百科的解释是这样的: 

套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。  
在我看来,套接字就是网络编程的接口,是连接网络的一种工具。用IP地址区分主机、用套接字区分主机里面的程序、用端口号区分套接字 
 可分配的端口号是0-65535,但0-1023是知名端口号,不能使用;TCP套接字和UDP套接字可以共用同一个端口号   
网络编程中接受连接请求的套接字创建过程可整理如下:   
Linux: 
首先介绍一下Linux相关知识: 
1、底层文件访问(Low-Level File Access)和文件描述符 
  底层;与标准无关的操作系统独立提供的 
  文件描述符(对应于window的句柄):系统分配给文件或套接字的整数 
   文件和套接字一般经过创建过程才会被分配文件描述符,输入输出对象即使未经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符。 
2、打开文件的函数: 
    int open(const char *path, int flag); 
成功时返回文件描述符,失败时返回-1. 
path为文件名的字符串地址(路径信息),flag文件打开模式信息 
3、关闭文件的函数 
       ssize_t write(int fd, const void * buf, size_t nbytes); 
       成功时返回写入的字节数,失败时返回-1 
       fd,显示数据传输对象的文件描述符 
       buf,保存要传输数据的缓冲地址值 
       nbytes,要传输数据的字节数  
         size_t是通过typedef声明的unsigned int类型,ssize_t代表signed int, 
s代表signed 
ssize_t、size_t都是元数据类型,操作体统定义的数据类型会添加后缀_t  
 
4、创建文件的函数
fd=open("data.txt",O_CREAT|O_WRONLY|O_TRUNC);
创建空文件,只写模式打开,若存在同名文件,则清空文件全部数据  
 
5、读取文件的函数 
  ssize_t read(int fd, void * buf, size_t nbytes); 
  成功时返回接收的字节数(文件结尾返回0),失败时返回-1 
       文件描述符从3开始以由小到大的顺序编号,0,1,2是分配给标准I/O的描述符
服务器端:
1、socket函数创建套接字 
  #include <sys/socket.h> 
  int socket(int domain, int type, int protocol); 
  成功时返回文件描述符,失败时返回-1.  
  domain,套接字中使用的协议族信息;type,套接字数据传输类型信息;protocol,计算机间通信中使用的协议信息 
  •  协议族(Protocol Family): 
PF_INET,IPV4互联网协议族 
PF_INET6,IPV6互联网协议族  
PF_LOCAL,本地通信的Unix协议族 
PF_PACKET,底层套接字的协议族 
PF_IPX,IPX Novell协议族 
套接字实际采用的最终协议信息是通过socket函数的第三个函数传递, 
在指定协议族范围内通过第一个参数决定第三个参数   

 套接字类型(Type) 
即套接字的数据传输方式,socket第二个参数传递 
协议族中可能存在多种数据传输方式 
 
  • 面向连接的套接字(基于字节)
   向socket第二个参数传递SOCK_STREAM,创建面向连接的套接字 
  特点:传输过程中数据不会丢失、按序传输数据、传输的数据不存在数据边界 
           套接字连接必须一一对应   
  收发数据的套接字内部有缓冲(buffer),即字节数组 
  面向连接的套接字会根据接收端的状态传输数据   
  int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
  • 面向消息的套接字 
  SOCK_DGRAM
  不可靠、不按顺序、无连接、有数据边界、以高速为目的 
  限制每次传输的数据大小  
  int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
  •    协议的最终选择 
  socket函数的第三个参数决定最终采用的协议,第三个参数大多取0, 
  除非同一协议族中存在多个数据传输方式相同的协议
2、bind函数分配地址信息(IP地址和端口号) 
  #include <sys/socket.h>  
  int bind(int sockfd, struct sockaddr *myaddr,  
socklen_t addrlen);  
  成功返回0,失败返回-1。  
  sockfd  要分配地址信息(IP地址和端口号)的套接字文件描述符 
myaddr  存有地址信息的结构体变量地址值
adddrien 第二个结构体变量的长度 
 地址信息的表示 
         AF_INET,IPV4网络协议中使用的地址族 
AF_INET6,IPV6网络协议中使用的地址族 
  •   相关结构体
struct sockaddr_in 

  sa_family_t    sin_family;//地址族 
  uint16_t       sin_port;//16位tcp/udp端口号,以网络字节序保存 
  struct in_addr sin_addr;//32位IP地址,以网络字节序保存 
  char           sin_zero[8];//不使用,必需为0, 
                                   为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员 
};
 
struct in_addr  

 in_addr_t  s_addr;//32位IPV4地址 
};  

POSIX,UNIX系列操作系统的可移植操作系统接口,定义了一些其他数据类型 
sa_family_t    地址族 
socklen_t      长度
in_addr_t      IP地址,声明为uint32_t 
in_port_t      端口号,声明为uint16_t
 
  • 网络字节序与地址变换 
         不同CPU中,4字节整数型值1在内存空间的保存方式是不同的 
         字节序和网络字节序: 
大端序:高位字节存放到低位地址 【高位字节值存放到低位地址,先存高位值】
小端序:高位字节存放到高位地址 
IntelCPU系列用小端  
网络字节序统一为大端序 
 
字节序转换: 
unsigned short htons(unsigned short); 
unsigned short ntohs(unsigned short); 
unsigned long htonl(unsigned long);  
unsigned long ntohl(unsigned long);
h代表主机,n代表网络,s指short,l指long,to就是to 
除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题 
  
 
  • 网络地址的初始化和分配  
         sockaddr_in中保存的地址信息的成员为32位整数型 
#include <arpa/inet.h> 
 
in_addr_t inet_addr(const char * string); 
成功返回 32位大端序整数型值,失败时返回INADDR_NONE 
还可以检测无效IP 
 
#include <arpa/inet.h> 
int inet_aton(const char * string, struct in_addr * addr); 
     成功返回1,失败返回0 
     string 含有需转换的IP地址信息的字符串地址值 
     addr   将保存转换结果的in_addr结构体变量的地址值  
aton和addr函数功能完全相同,利用了in_addr结构体,使用频率更高, 
Windows没有此函数 
 
 
     网络地址初始化【主要针对服务器】 
struct sockaddr_in addr; 
char * serv_ip = "211.217.168.13"; //声明IP地址字符串
char * serv_port = "9190";  //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员置零
     addr.sin_family = AF_INET; //指定地址族
     addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化 
     addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化
利用字符串格式的IP地址和端口号初始化了sockaddr_in结构体变量 
 
客户端地址信息初始化 
声明sockaddr_in结构体,初始化为要与之连接的服务器端套接字的IP和端口号, 
再调用connect 
 
INADDR_ANY 
addr.sin_addr.s_addr = htonl(INADDR_ANY); 
自动获取运行服务器端的计算机IP地址 
同一计算机可以分配多个IP地址,个数与NIC相等 
3、listen函数设置监听【将套接字转化为可接收请求状态】 
  #include <sys/socket.h> 
  int listen(int sockfd, int backlog); 
  成功返回0,失败返回-1   
     sock  希望进入等待连接请求状态的套接字文件描述符 
     传递的描述符套接字参数成为服务器端套接字(监听套接字)
            backlog  连接请求等待队列(Queue)的长度,若为5,则队列长度为5 ,表示最多使5个连接请求进入队列 
4、accept函数受理连接请求 
  #include <sys/socket.h> 
  int accept(int sockfd, struct sockaddr *addr,  
  socklen_t *addrlen); 
  成功时返回文件描述符,失败时返回-1.  
sock  服务器套接字的文件描述符 
  addr  保存发起连接请求的客户端地址信息的变量地址值 
调用函数后向传递来的地址变量参数填充客户端地址信息 
  addrlen  第二个参数addr结构体的长度,但是存有长度的变量地址。 
  函数调用完成后,该变量即被填入客户端地址长度
 
  函数调用成功时,accept函数内部将产生用于数据I/O的套接字 
  并返回其文件描述符,套接字自动创建并自动与发起连接的客户端建立连接 
客户端:   
#include <sys/socket.h> 
          int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen); 
          成功返回0,失败返回-1。 
 sock  客户端套接字的文件描述符 
 servaddr  保存目标服务器端地址信息的变量地址值 
 addrlen  以字节为单位传递已传递给第二个结构体参数 
          servaddr的地址变量长度 
   
 客户端调用connect函数后,服务器端接收连接请求或中断连接请求才会返回 
 客户端的IP地址和端口在调用connect函数时自动分配  
以下是一份简单的代码:
#include <stdio.h>  
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h>  
#include <arpa/inet.h> 
#include <sys/socket.h> 
void error_handling(char *message);  


int main(int argc, char* argv[]) 
{  
  int sock; 
  struct sockaddr_in serv_addr; 
  char message[30]; 
  int str_len; 
   
  if(argc!=3) 
  { 
    printf("Usage : %s <IP> <port>\n", argv[0]); 
    exit(1);
  } 
  
  sock=socket(PF_INET, SOCK_STREAM, 0); //建立TCP套接字
  if(sock == -1) 
     error_handling("socket() error"); 
   
  memset(&serv_addr, 0, sizeof(serv_addr)); //地址信息初始化
  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(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)//请求连接 
     error_handling("connect() error!");  
 
  str_len=read(sock, message, sizeof(message)-1); //读取数据
  if(str_len==-1) 
     error_handling("read() error!"); 
 
  printf("Message from server : %s \n", message); 
  close(sock); //关闭套接字
  return 0; 
}  
 
void error_handling(char *message) 

  fputs(message, stderr); 
  fputc('\n', stderr); 
  exit(1);
}


 
Windows:
1、winsock编程时,首选必须调用WSAStartup函数 
#include <winsock2.h> 
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); 
成功时返回0,失败时返回非零的错误代码值 
wVersionRequested,程序员要用的Winsock版本信息 
lpWSAData,WSADATA结构体变量的地址值 
套接字版本信息应该准备WORD(unsigned short)类型的,传递给该函数第一个参数wVersionRequested,版本号为1.2,则传递0x0201 
可用MAKEWORD宏函数构建WORD型版本信息 
MAKEWORD(1, 2);版本为1.2; 

2、注销库 
#include <winsock2.h> 
int WSACleanup(void); 
成功时返回0,失败时返回SOCKET_ERROR 
调用该函数,winsock相关库将归还给Windows操作系统,多在程序结束之前调用   

3、相关函数 
创建套接字 
#include <winsock2.h> 
 
SOCKET socket(int af, int type, int protocol); 
成功时返回套接字句柄,失败时返回INVALID_SOCKET 
 
分配IP和端口 
int bind(SOCKET s, const struct sockaddr * name, int namelen); 
成功时返回0,失败返回SOCKET_ERROR 
 
激活套接字,使可接收客户端连接
int listen(SOCKET s,int backlog); 
成功时返回0,失败返回SOCKET_ERROR 
 
 
调用受理客户端连接请求
SOCKET accept(SOCKET s,struct sockaddr * addr, int * addrlen); 
成功时返回套接字句柄,失败时返回INVALID_SOCKET 
 
客户端发送连接请求 
connect(SOCKET s, const struct sockaddr * name,int namelen); 
 
关闭套接字 
int closesocket(SOCKET s); 
成功返回0,失败返回SOCKET_ERROR  
      
不同于Linux,Windows的文件句柄相关函数和套接字句柄相关函数是有区别的 
 
4、基于Windows的I/O函数 
Windows严格区分套接字I/O函数和文件I/O函数 
Windows数据传输函数 
int send(SOCKET s, const char * buf, int len, int flags); 
成功返回传输字节数,失败返回SOCKET_ERROR 
s,数据传输对象连接的套接字句柄值 
buf,保存待传输数据的缓冲地址值 
len,要传输的字节数 
flag,传输数据时用到的多种选项信息 
 
     int recv(SOCKET s,const char * buf,int len,int flags); 
成功返回接收的字节数(收到EOF返回0),失败返回SOCKET_ERROR 
以下是Windows的代码: 
#include <stdio.h>  
#include <stdlib.h>  
#include <WinSock2.h>  
void ErrorHandling(char* message);  
  
int main(int argc, char *argv[]) {  
    WSADATA wsaData;  
    SOCKET hSocket;  
    SOCKADDR_IN servAddr;  
  
    char message[30];  
    int strlen;  
    if (argc != 3) {  
        printf("Usage: %s <IP> <port>\n", argv[0]);  
        exit(1);  
    }  
      
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)  
        ErrorHandling("WSAStartup() error!");  
  
    hSocket = socket(PF_INET, SOCK_STREAM, 0);  
    if (hSocket == INVALID_SOCKET)  
        ErrorHandling("socket() error!");  
  
    memset(&servAddr, 0, sizeof(servAddr));  
    servAddr.sin_family = AF_INET;  
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);  
    servAddr.sin_port = htons(atoi(argv[2]));  
  
    if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)  
        ErrorHandling("connect() error!");  
  
    strlen = recv(hSocket, message, sizeof(message) - 1, 0);  
    if (strlen == -1)  
        ErrorHandling("read() error!");  
  
    printf("Message from server:%s\n", message);  
    closesocket(hSocket);  
    WSACleanup();  
    return 0;  
}  
  
void ErrorHandling(char * message) {  
    fputs(message, stderr);  
    fputc('\n', stderr);  
    exit(1);  
}  


阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 肠道菌群紊乱怎么办 电话卡芯片坏了怎么办 电视机芯片坏了怎么办 u盘格式化怎么办打不开 u盘不能格式化怎么办 汽车遥控器丢了怎么办 银行卡芯片坏了怎么办 大脑思维被监控怎么办 基因检测高风险怎么办 指纹感应器坏了怎么办 手机指纹没反应怎么办 激光后白色疤痕怎么办 手机传感器坏了怎么办 手机感应器坏了怎么办 多肉叶片长根后怎么办 绿植叶子发黄怎么办 茉莉的叶子发黄怎么办 靶向药耐药了怎么办 靶向治疗耐药了怎么办 秸秆焚烧我们该怎么办 卫生间往外渗水怎么办 劳务资质取消后怎么办 太阳能里有水垢怎么办 珍珠白车漆发黄怎么办 厨房没做防水怎么办 阳台防水没做好怎么办 苹果手表进水了怎么办 水泥天面漏水怎么办 房子渗水墙体湿怎么办 忘记社保电脑号怎么办 餐饮加盟被骗了怎么办 镜头镀膜刮花怎么办 不锈钢喷不上漆怎么办 不锈钢刮花了怎么办 三星误删系统怎么办 lv涂层帆布开裂怎么办 水泥池起绿藻怎么办 钢化膜边角不粘怎么办 钢化膜没有贴好怎么办 手机膜没有粘性怎么办 做防水地漏箅子怎么办