1.客户端:
socket()-connect()-read()/write()/send()/recv()-close()
2.服务器:
socket()-bind()-listen()-accept()-read()/write()/send()/recv()-close()
3.socket的填充内容,注意其中一个地址信息结构体:struct sockaddr
我们一般不对这个结构体操作。而是对其他结构体赋值,最后再强制转换为(struct sockadd)类型就可以了。
4.注意对长等待函数(阻塞模式)需要考虑中断信号的影响
5.close(fd)函数对fd的关联计数器-1,只有当计数器=0时才关闭fd。因此子进程继承了父进程打开的fd,但是却用不上时需要关闭继承过来的fd。
6.accept()函数传入的是监听套接字,返回是一个全新的连接套接字。2者的fd不同,也即这2个套接字是不同的!
下面代码以TCP通信为例:
/* 头文件 MYHEAD.h */
#ifndef _MYHEAD_H_
#define _MYHEAD_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <wait.h>
#include <errno.h>
#include <arpa/inet.h>
#endif
#include "MYHEAD.h"
//使用示例:./client 192.168.1.100 5001
int main(char argc,char **argv){
/* 判断输入参数个数 */
if(argc<3){
printf("Usage:%s <ip> <port>\n",argv[0]);
exit(-1);
}
int fd ;
/* 创建socket */
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
uint32_t ip; //容器
/* 192.xxx.xxx.xxx字符串形式,转换,32位,网络字节序 */
if(inet_pton(AF_INET,argv[1],(void *)&ip) != 1){
perror("inet_pton");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ip;
addr.sin_port = htons(atoi(argv[2])); //网络字节序
/* 链接connect */
if(connect(fd,(struct sockaddr *)&addr,sizeof(addr))<0){
perror("connect");
exit(-1);
}
char buf[30];
int n;
while(1){
if(fgets(buf,30,stdin)<0){ //返回0代表没有,返回-1代表出错
perror("fgets");
exit(-1);
}
if((n=write(fd,buf,strlen(buf))<0)){ //返回实际写入字节数或EOF
perror("write");
exit(-1);
}
if(strcmp(buf,"quit\n") == 0){ //细节:字符串末尾\n
close(fd);
exit(0);
}
}
}
#include "MYHEAD.h"
#define LOCAL_PORT 5001
#define BACKLOG 5
void child_process(int fd,struct sockaddr_in client); //子进程
void handler(int signal); //响应函数
int main(char argc,char **arcv)
{
int fd,newfd;
pid_t pid;
/* 绑定:信号--响应函数。子进程退出时,内核会发送SIGCHLD通知父进程 */
signal(SIGCHLD,handler);
/* 创建socket */
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
/* 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCAL_PORT); //字节序转换
addr.sin_addr.s_addr = htonl(INADDR_ANY); //(INADDR_ANY)表示本机任意网卡地址
/* 绑定服务器的ip,port给刚创建的socket */
if(bind(fd,(struct sockaddr *)&addr,sizeof(addr)) < 0)
{
perror("bind");
exit(-1);
}
/* 监听,socket变成被动的 */
if(listen(fd,BACKLOG) < 0) //BACKLOG=同时请求的客户端个数
{
perror("listen");
exit(-1);
}
socklen_t client_len; //容器
struct sockaddr_in client; //容器,存放客户端的地址信息
while(1){
/* 阻塞等待建立链接accept,fd属于监听类socket(被动),newfd属于连接类socket(主动) */
if((newfd = accept(fd,(struct sockaddr *)&client,&client_len))<0)
{
if(errno != EINTR){ //accept属于阻塞函数,要考虑被中断信号打断的情况。
perror("accept");
exit(-1);
}
}
printf("newfd:%d,fd:%d\n",newfd,fd); //每个进程都有一个独立的表,因此每个进程打印出来的fd数值可以相同
if((pid=fork())<0){
perror("fork");
exit(-1);
}
if(0 == pid){ //子进程
close(fd); //关闭监听socket,对子进程来说不需要监听
child_process(newfd,client);
}else{ //父进程,只负责accept
close(newfd); //关闭连接socket,对父进程来说不要连接
}
}
close(fd); //父进程关闭监听socket
return 0;
}
/* 子进程函数 */
void child_process(int fd,struct sockaddr_in client){
char buf[30];
int n;
while(1){
memset(buf,0,30); //清0
if((n=read(fd,buf,30))<0){ //阻塞等待
if(n!=EINTR) //阻塞可能会被中断信号打断,并返回-1
{ //任何长等待机制都要考虑被中断的处理方式,用来区分异常错误
perror("read");
exit(-1);
}
}
if(0 == strcmp(buf,"quit\n")){ //细节:字符串末尾的\n
printf("client %d ,quit\n",fd);
close(fd);
exit(0);
}
if(n>0){
printf("client_ip:%d\n",ntohl(client.sin_addr.s_addr));
printf("client_port:%d\n",ntohs(client.sin_port));
printf(">>%s",buf);
}
}
}
/* 信号响应函数 */
void handler(int signal){
if(SIGCHLD == signal){
waitpid(-1,NULL,WNOHANG); //WNOHANG代表非阻塞。
}
}