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
#include
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
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
第一个参数使用调用socket函数的返回值,第二个参数使用定义好的sockaddr*结构体,第三个参数是结构体的长度。
成功返回0,失败返回-1.
listen函数:
#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
该函数并不做阻塞监听,而是设置一个“同时”能够建立连接的最大数量。
注意,它也不是设置最大支持连接。能够支持的最大连接数量是系统内核决定。
因为连接建立需要3次握手,这个过程需要时间,listen设置的是该时间内同时允许建立握手队列量。
accept 函数:
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第2个参数是传出参数,返回链接客户端的地址,包括ip与端口号,无需自己去初始化。
成功返回一个新的socket文件描述符,与前面socket函数返回的描述符不是一个东西。
新的文件描述符用于和客户端通信。、
connect函数:
#include
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
差异一:客户端不用bind绑定端口号,而是用connect来连接绑定,这两个函数的参数基本相同。服务器端用bind绑定ip地址与端口号。
差异二:服务器端多了listen和accept两个函数。
尝试编写服务器端程序:
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#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
编译成功,如何测试服务器能否正确运行呢?
可以使用nc命令来模拟客户端,运行server程序,再开一个终端。
实验成功。