1.基本TCP连接
1.1 基本TCP客户/服务器程序流程
1.2基本函数
根据连接流程图中顺序介绍:
- socket()
#include <sys/socket.h>
int socket (int family, int type, int protocol);
/*Returns: non-negative descriptor if OK, -1 on error*/
- 作用:建立一个套接字,并返回一个描述符以方便使用。可以类比为创建了一个文件,使用文件标识符就可以操作文件。
- 参数(family):“protocol family”,常用协议以下两种:
协议 | 参数 |
---|---|
IPv4 | AF_INET |
IPv6 | AF_INET6 |
- 参数(type):“type of socket”,常用类型以下两种:
类型 | 参数 |
---|---|
数据流 | SOCK_STREAM |
数据报 | SOCK_DGRAM |
- 参数(protocol):“protocol of sockets”,传输协议类型,常用有以下三种:
传输协议 | 参数 |
---|---|
TCP | IPPROTO_TCP |
UDP | IPPRTO_UDP |
SCTP | IPPROTO_SCTP |
0 | 由family和type确定的默认值 |
最常用的参数组合为: socket(AF_INET,SOCK_STREAM,0)
- bind()
#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
//Returns: 0 if OK,-1 on error
- 作用:一个完整的套接字应该包括套接字种类、IP地址、端口号(客户端无需指定)。套接字种类由socket()指定,IP地址和端口号则由bind()确定。
- 参数(sockfd):由Socket()创建的套接字描述符,指向该套接字。
- 参数(const struct sockaddr *myaddr),传入协议指定的地址参数(包括IP地址、端口号等)。注意该参数定义的结构体为通用地址结构,即可以兼容多种协议地址格式,每种特定协议的地址结构体传入时需做强制类型转换。
/*通用套接字地址结构体:*/
struct sockaddr {
uint8_t sa_len;/*length of structure*/
sa_family_t sa_family; /*address family:AF_xxx */
char sa_data[14]; /*protocol-specific address*/
/*IPv4套接字结构体*/
struct in_addr{
in_addr_t s_addr; /*32-bit IPv4 address (network byte ordered)*/
}
struct sockaddr_in {
uint8_t sin_len;/*length of structure [16] */
sa_family_t sin_family;/*AF_INET*/
in_port_t sin_port;/*16-bit port number(network byte ordered)*/
struct in_addr sin_addr;/设置为全0,则监听该服务器的所有网卡(IP)/
char sin_zero[8];/*unused*/
}
- listen()
#include <sys/socket.h>
#int listen (int sockfd, int backlog);
//Returns: 0 if OK, -1 on error
- 作用:将套接字转变为被动接受状态,指示内核接受指向该套接字的连接,将接收到的连接放入队列中,等候accept。
- 参数(backlog):接受连接队列的最大长度,即连接的最大个数。
- accept()
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
//Returns: non-negative descriptor if OK, -1 on error
- 作用:调用TCP服务并返回已完成连接队列中第一个连接;如果已连接的队列为空,则该进程被置为休眠状态。如果accept成功,返回一个代表该TCP连接的描述符。
- 参数(struct sockaddr *cliaddr):客户端地址结构,从内核传输到进程。如不需要客户端地址信息可置为空,不影响连接及数据传输。
- 参数(socklen_t *addrlen):值-结果(value-result)参数;有两个作用,一是传入地址结构体的大小,二是传出实际地址结构大小。采用指针是便于作用二的实现,即参数传出。如不需要客户端地址信息可置为空。
- 注:Socket创建的接口描述符也称为监听套接字,一个服务器通常只创建一个监听套接字,可接受多个连接。accept创建的接口描述符也被称为连接套接字,内核为每个连接创建一个连接套接字,当连接结束时该连接套接字也被关闭。
- read()/readn() 与 write()/writen()
//这两个函数使用方法一样,传入参数:(连接描述符,接收数据地址,接收数据个数),返回值为实际接收的数据个数。
read (int __fd, void *__buf, size_t __nbytes);//库函数
readn(int fd, void *vptr, size_t n);//unix网络编程定义
//这两个函数使用方法一样,传入参数:(连接描述符,发送数据地址,发送数据个数),返回值为实际发送的数据个数。
write (int __fd, const void *__buf, size_t __n);//库函数
writen(int fd, const void *vptr, size_t n);//unix网络编程定义
字节流套接字上的read/write函数不同于文件io中的read函数,由于内核中套接字缓冲区的原因,在字节流上套接字上的read/write可能输出的字节数比请求的字节数少,需要多次调用read/write函数完成接收/发送数据。为了避免接收/发送数据时重复调用函数导致代码不整洁,unix网络编程书中定义了readn/writen函数,用于处理EINTR错误和重复调用read/write函数直至接收/发送完成。readn和writen函数定义如下:
readn(int fd, void *vptr, size_t n){
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
/* end readn */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */
- close()
#include <unistd.h>
int close (int sockfd);
//Returns: 0 if OK, -1 on error
作用:在当前线程中关闭套接字,并对套接字的引用计数减一(当套接字引用计数为零时关闭连接)。
1.3 其它常用函数
- 地址转换函数
- 网络字节序二进制地址形式转换为xxx.xxx.xxx.xxx形式(inet_ntop)
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//Returns: pointer to result if OK, NULL on error
- xxx.xxx.xxx.xxx地址形式转换为网络字节序二进制地址形式(inet_pton)
int inet_pton(int family, const char *strptr, void *addrptr);
//Returns: 1 if OK, 0 if input not a valid presentation format, -1 on error
//输出的二进制
2.网络字节顺序与本地字节顺序之间的转换函数
- 主机转网络字节序 :
htonl() – “Host to Network Long”
htons() – “Host to Network Short” - 网络转主机字节序
ntohl() – “Network to Host Long”
ntohs( )-- “Network to Host Short”
2. 多线程(并发)
2.1基本函数
- fork()
#include <unistd.h>
pid_t fork(void);
//Returns: 0 in child, process ID of child in parent, -1 on error
- 作用:创建一个和原进程几乎一样的子进程。
- 示例:
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t childpid; //fork函数返回值:在父进程中是子进程号;在子进程中为0;若为-1则代表fork失败。
int count=0;
childpid=fork();
if (childpid < 0)
{
printf("error:can't fork!");
}
else if (childpid == 0)
{
printf("I'm the child process, my process id is %d\n",getpid());
printf("childpid=%d\n",childpid);
count++;
}
else
{
printf("I'm the parent process, my process id is %d\n",getpid());
printf("childpid=%d\n",childpid);
count++;
}
printf("程序运行计数count= %d\n",count);
return 0;
}