基本套接字函数
github地址 https://github.com/can130/Socket
书:http://chuquanl.com/?page_id=69
1.socket()
#include <sys/socket.h>
int socket (int family, int type, int protocol);
//Returns: non-negative descriptor if OK, -1 on error
2.connect函数
TCP客户使用connect函数来建立与tcp服务器的连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
//Returns: 0 if OK, -1 on error
sockfd is a socket descriptor returned by the socket function. The second and third arguments are a pointer to a socket address structure and its size.
3.bind函数
bind函数把一个本地协议地址赋予一个套接字。对于网际地址,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCp和udp端口号的组合
#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
// Returns: 0 if OK,-1 on error
对于IPv4地址来说,通配地址通常由常值INADDR_ANY来指定,其值一般为0.他告知内核去选择IP地址,使用如下:
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY); /* wildcard */
对于IPv6
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any; /* wildcard */
4.listen函数
listen函数仅由tcp服务器调用,做两件事情
1. 当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,他是一个即将调用connect发起连接的客户套接字,listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受向该套接字的连接请求,调用listen后导致套接字从closed状态转换到listen状态。
- listen函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数
#include <sys/socket.h>
#int listen (int sockfd, int backlog);
//Returns: 0 if OK, -1 on error
本函数应该在调用socket和bind函数之后,并在调用accept函数之前调用。
理解backlog参数
5.accept函数
accept由tcp服务器调用,用于从已完成连接队列头返回下一个已完成连接,如果已完成队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞方式)
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
//Returns: non-negative descriptor if OK, -1 on error
参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址。
addrlen是值-结果参数:调用前由他所引用的整数值作为由cliaddr所指的套接字地址结构长度,返回时,该整数值为内核存放在该套接字地址结构内的准确字节数
如果accept成功,返回内核自动生产的一个全新描述符,代表所返回客户的TCP连接。
第一个参数称为监听套接字(由socket创建,随后也用作bind和listen的地一个参数)
example 显示客户端IP地址和端口号的时间获取服务程序
服务端程序
#include <time.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h> //htons
#include <strings.h> //bzero
#include<string.h>
#include<stdio.h>
#include <unistd.h> //close
#define MAXLINE 4096 /* max text line length */
#define LISTENQ 1024 /* 2nd argument to listen() */
#define PORT 6000
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t len;
struct sockaddr_in servaddr, cliaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT); /* daytime server */
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
for ( ; ; ) {
len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
printf("connection from %s, port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));
close(connfd);
}
}
客户端口
#include <time.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h> //htons
#include <strings.h> //bzero
#include<string.h>
#include<stdio.h>
#include <unistd.h> //close
#include "apue.h" //err_
#define MAXLINE 4096 /* max text line length */
#define LISTENQ 1024 /* 2nd argument to listen() */
#define PORT 6000
int main(int argc, char **argv)
{
int sockfd, n, counter = 0;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: a.out <IPaddress>");
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT); /* daytime server */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
counter++;
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
printf("counter = %d\n", counter);
return 0;
}
结果
服务端
can@CAN:~/code/unpv13e/intro$ ./daytimetcpsrv1.out
connection from 127.0.0.1, port 55030
connection from 192.168.2.108, port 52000
客户端
can@CAN:~/code/unpv13e/intro$ ./daytimetcpcli1.out 127.0.0.1
Sun Apr 9 12:15:43 2017
counter = 1
can@CAN:~/code/unpv13e/intro$ ./daytimetcpcli1.out 192.168.2.108
Sun Apr 9 12:15:50 2017
counter = 1
典型的并发服务器程序轮廓
当建立一个连接时,accept返回,紧接着调用fork,由子进程服务客服,父进程等待另一个连接,新的客户由子进程提供服务,父进程则关闭已连接套接字(fork的时候将资源复制了一份,子父共享述符)
pid_t pid;
int
listenfd,
connfd;
listenfd = Socket( ... );
/* fill in sockaddr_in{} with server's well-known port */
Bind(listenfd, ... );
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept (listenfd, ... ); /* probably blocks */
if( (pid = Fork()) == 0) {
Close(listenfd);
doit(connfd);
Close(connfd);
exit(0);
/* child closes listening socket */
/* process the request */
/* done with this client */
/* child terminates */
}
Close(connfd);
/* parent closes connected socket */
}
getsockname函数和getpeername函数
返回某个套接字关联的本地地址(getsockname) 返回与某个套接字关联的外地地址(getpeername)
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
//Both return: 0 if OK, -1 on error
两个函数的最后一个参数都是值-结果参数
example 获取套接字的地址族
编写一个函数 返回套接子的地址族
int
sockfd_to_family(int sockfd)
{
struct sockaddr_storage ss;
socklen_t len;
len = sizeof(ss);
if (getsockname(sockfd, (struct sockaddr *) &ss, &len) < 0)
return(-1);
return(ss.ss_family);
}
shutdown 函数
终止网络连接的通常方法是调用close函数。不过close函数有两个限制,却可以使用shutdown来避免。
1. close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字。而shutdown不管引用计数
2. close终止读和写两个方向的数据传送。既然tcp是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据发改给我们
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
//Returns: 0 if OK, –1 on error