struct sockaddr 结构体,早期网络编程函数都是基于该结构体,但是随着技术的发展,ipv4协议诞生,为了向前兼容,现在sockaddr退化成了void * 作用的指针,内部会强制类型转换为所需的地址类型(sockaddr_in或者sockaddr_un或者scokaddr_in6) sockaddr_in代表AF_INET,ipv4协议,sockaddr_un代表AF_UNIX,scokaddr_in6代表AF_INET6
定义的时候,应该定义成struct sockaddr_in,,而在实际使用时,传递参数需要强制转换一下struct sockaddr *。
bind函数,accept函数,connect函数调用时会遇到这个问题。
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ };sin_port端口号,注意网络字节序与本机字节序的转换。 sin_addr ip地址,注意网络字节序与本机字节序的转换。它是个结构体,成员只有一个是
struct in_addr { uint32_t s_addr; /* address in network byte order */ };它在赋值时,可能有以下几种情况
struct sockaddr_in addr; addr.sin_family= AF_INET/AFINET6; addr.sin_port=htons/ntohs; addr.sin_addr.s_addr=inet_pton/inet_ntop;核心函数socket(int domain, int type, int protocol);
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);第一个参数domain取值: AF_INET AF_INET6 AF_UNIX (本地套接字)
第二个参数type取值: SOCK_STREAM 可靠的,基于顺序的字节流TCP。 SOCK_DGRAM 无连接的、不可靠的udp SOCK_SEQPACKET 双线路,可靠,发送固定长度的数据包 SOCK_RAW 使用ICMP公共协议
第三个参数一般取0,表示使用默认。
函数调用成功返回新创建的socket文件描述符,失败返回-1
bind函数
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);第一个参数使用调用socket函数的返回值,第二个参数使用定义好的sockaddr*结构体,第三个参数是结构体的长度。 成功返回0,失败返回-1.
listen函数:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);该函数并不做阻塞监听,而是设置一个“同时”能够建立连接的最大数量。 注意,它也不是设置最大支持连接。能够支持的最大连接数量是系统内核决定。 因为连接建立需要3次握手,这个过程需要时间,listen设置的是该时间内同时允许建立握手队列量。
accept 函数:
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);第2个参数是传出参数,返回链接客户端的地址,包括ip与端口号,无需自己去初始化。 成功返回一个新的socket文件描述符,与前面socket函数返回的描述符不是一个东西。 新的文件描述符用于和客户端通信。、
connect函数:
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);差异一:客户端不用bind绑定端口号,而是用connect来连接绑定,这两个函数的参数基本相同。服务器端用bind绑定ip地址与端口号。 差异二:服务器端多了listen和accept两个函数。
尝试编写服务器端程序:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/socket.h> #define SERV_PROT 6666 #define SERV_IP "127.0.0.1" int main() { int serfd; struct sockaddr_in serv_addr; //填充结构体,注意转换 serv_addr.sin_fmaily=AF_INET; serv_addr.sin_port=htons(SERV_PROT); serv_addr.sin_addr.s_addr=inet_pton(SERV_IP); serfd=socket(AF_INET,SOCK_STREAM,0); //绑定,强制类型转换 bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); return 0; }添加listen,128是默认值。 添加accept,注意函数的用法。
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/socket.h> #define SERV_PROT 6666 #define SERV_IP "127.0.0.1" int main() { int serfd,clifd; struct sockaddr_in serv_addr,cli_addr; socklen_t cli_addr_len; cli_addr_len=sizeof(cli_addr); //填充结构体 serv_addr.sin_fmaily=AF_INET; serv_addr.sin_port=htons(SERV_PROT); serv_addr.sin_addr.s_addr=inet_pton(SERV_IP); serfd=socket(AF_INET,SOCK_STREAM,0); bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); listen(serfd,128); //用clifd接受accept的返回,第二个参数是传出参数,接受客户端的ip和端口,第三个参数是结构体大小。 //注意加上取地址符,因为是传出参数。 clifd=accept(serfd,&cli_addr,&cli_addr_len); return 0; }添加业务逻辑,把每一个小写的字符都转换成大写字符。 添加必备的头文件。 修改几处bug,比如inet_pton使用错误,accept使用错误
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/socket.h> #include <arpa/inet.h> #include <ctype.h> #define SERV_PROT 6666 #define SERV_IP "127.0.0.1" int main() { int serfd,clifd; struct sockaddr_in serv_addr,cli_addr; socklen_t cli_addr_len; cli_addr_len=sizeof(cli_addr); char buf[BUFSIZ]; int i,n; char * dst; serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERV_PROT); //有两种写法,一种是用inet_pton,另一种是利用已经定义好的宏 //serv_addr.sin_addr.s_addr=inet_pton(AF_INET,SERV_IP,dst); serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serfd=socket(AF_INET,SOCK_STREAM,0); bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); listen(serfd,128); clifd=accept(serfd,(struct sockaddr*)&cli_addr,&cli_addr_len); //业务逻辑 n=read(clifd,buf,sizeof(buf)); for(i=0;i<n;i++) { buf[i]=toupper(buf[i]); } write(clifd,buf,n); close(serfd); close(clifd); return 0; }编译成功,如何测试服务器能否正确运行呢? 可以使用nc命令来模拟客户端,运行server程序,再开一个终端。 实验成功。